Merge "Fix build with ToT libc++" into main am: dbecfa8c6e am: a99e1cd67b

Original change: https://android-review.googlesource.com/c/platform/frameworks/native/+/3100617

Change-Id: I9237b05efe647e6c779c7eec52ee8008e144228c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 8c4dfbb..2520a71 100644
--- a/Android.bp
+++ b/Android.bp
@@ -105,7 +105,13 @@
 
 cc_library_headers {
     name: "libandroid_headers_private",
+    host_supported: true,
     export_include_dirs: ["include/private"],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
 
 filegroup {
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index 4a77967..a1c10f5 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -114,6 +114,7 @@
         "libincidentcompanion",
         "libdumpsys",
         "libserviceutils",
+        "android.tracing.flags_c_lib",
     ],
 }
 
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index ba0a38a..a8d12a1 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -39,6 +39,7 @@
     std::string calling_package;
     int32_t user_id = -1;
     bool keep_bugreport_on_retrieval = false;
+    bool skip_user_consent = false;
 };
 
 static binder::Status exception(uint32_t code, const std::string& msg,
@@ -62,7 +63,8 @@
 
 [[noreturn]] static void* dumpstate_thread_retrieve(void* data) {
     std::unique_ptr<DumpstateInfo> ds_info(static_cast<DumpstateInfo*>(data));
-    ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package, ds_info->keep_bugreport_on_retrieval);
+    ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package,
+    ds_info->keep_bugreport_on_retrieval, ds_info->skip_user_consent);
     MYLOGD("Finished retrieving a bugreport. Exiting.\n");
     exit(0);
 }
@@ -116,7 +118,8 @@
                                                 int bugreport_mode,
                                                 int bugreport_flags,
                                                 const sp<IDumpstateListener>& listener,
-                                                bool is_screenshot_requested) {
+                                                bool is_screenshot_requested,
+                                                bool skip_user_consent) {
     MYLOGI("startBugreport() with mode: %d\n", bugreport_mode);
 
     // Ensure there is only one bugreport in progress at a time.
@@ -151,7 +154,7 @@
 
     std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
     options->Initialize(static_cast<Dumpstate::BugreportMode>(bugreport_mode), bugreport_flags,
-                        bugreport_fd, screenshot_fd, is_screenshot_requested);
+                        bugreport_fd, screenshot_fd, is_screenshot_requested, skip_user_consent);
 
     if (bugreport_fd.get() == -1 || (options->do_screenshot && screenshot_fd.get() == -1)) {
         MYLOGE("Invalid filedescriptor");
@@ -207,6 +210,7 @@
     android::base::unique_fd bugreport_fd,
     const std::string& bugreport_file,
     const bool keep_bugreport_on_retrieval,
+    const bool skip_user_consent,
     const sp<IDumpstateListener>& listener) {
 
     ds_ = &(Dumpstate::GetInstance());
@@ -216,6 +220,7 @@
     ds_info->calling_package = calling_package;
     ds_info->user_id = user_id;
     ds_info->keep_bugreport_on_retrieval = keep_bugreport_on_retrieval;
+    ds_info->skip_user_consent = skip_user_consent;
     ds_->listener_ = listener;
     std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
     // Use a /dev/null FD when initializing options since none is provided.
@@ -223,7 +228,7 @@
         TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC)));
 
     options->Initialize(Dumpstate::BugreportMode::BUGREPORT_DEFAULT,
-                        0, bugreport_fd, devnull_fd, false);
+                        0, bugreport_fd, devnull_fd, false, skip_user_consent);
 
     if (bugreport_fd.get() == -1) {
         MYLOGE("Invalid filedescriptor");
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 7b76c36..c99f70e 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -44,7 +44,7 @@
                                   android::base::unique_fd bugreport_fd,
                                   android::base::unique_fd screenshot_fd, int bugreport_mode,
                                   int bugreport_flags, const sp<IDumpstateListener>& listener,
-                                  bool is_screenshot_requested) override;
+                                  bool is_screenshot_requested, bool skip_user_consent) override;
 
     binder::Status retrieveBugreport(int32_t calling_uid,
                                      const std::string& calling_package,
@@ -52,6 +52,7 @@
                                      android::base::unique_fd bugreport_fd,
                                      const std::string& bugreport_file,
                                      const bool keep_bugreport_on_retrieval,
+                                     const bool skip_user_consent,
                                      const sp<IDumpstateListener>& listener)
                                      override;
 
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index 97c470e..3b8fde9 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -96,7 +96,8 @@
     void startBugreport(int callingUid, @utf8InCpp String callingPackage,
                         FileDescriptor bugreportFd, FileDescriptor screenshotFd,
                         int bugreportMode, int bugreportFlags,
-                        IDumpstateListener listener, boolean isScreenshotRequested);
+                        IDumpstateListener listener, boolean isScreenshotRequested,
+                        boolean skipUserConsent);
 
     /**
      * Cancels the bugreport currently in progress.
@@ -130,5 +131,6 @@
                            FileDescriptor bugreportFd,
                            @utf8InCpp String bugreportFile,
                            boolean keepBugreportOnRetrieval,
+                           boolean skipUserConsent,
                            IDumpstateListener listener);
 }
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index c501921..d5125f0 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -17,49 +17,9 @@
 #define LOG_TAG "dumpstate"
 #define ATRACE_TAG ATRACE_TAG_ALWAYS
 
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <libgen.h>
-#include <limits.h>
-#include <math.h>
-#include <poll.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mount.h>
-#include <sys/poll.h>
-#include <sys/prctl.h>
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/wait.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <string.h>
-#include <sys/capability.h>
-#include <sys/inotify.h>
-#include <sys/klog.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <chrono>
-#include <cmath>
-#include <fstream>
-#include <functional>
-#include <future>
-#include <memory>
-#include <numeric>
-#include <regex>
-#include <set>
-#include <string>
-#include <utility>
-#include <vector>
+#include "dumpstate.h"
 
 #include <aidl/android/hardware/dumpstate/IDumpstateDevice.h>
-#include <android_app_admin_flags.h>
 #include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
@@ -74,6 +34,8 @@
 #include <android/hardware/dumpstate/1.1/types.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
 #include <android/os/IIncidentCompanion.h>
+#include <android_app_admin_flags.h>
+#include <android_tracing.h>
 #include <binder/IServiceManager.h>
 #include <cutils/multiuser.h>
 #include <cutils/native_handle.h>
@@ -81,21 +43,60 @@
 #include <cutils/sockets.h>
 #include <cutils/trace.h>
 #include <debuggerd/client.h>
+#include <dirent.h>
 #include <dumpsys.h>
 #include <dumputils/dump_utils.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <hardware_legacy/power.h>
 #include <hidl/ServiceManagement.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <limits.h>
 #include <log/log.h>
 #include <log/log_read.h>
+#include <math.h>
 #include <openssl/sha.h>
+#include <poll.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 #include <serviceutils/PriorityDumper.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/inotify.h>
+#include <sys/klog.h>
+#include <sys/mount.h>
+#include <sys/poll.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
 #include <utils/StrongPointer.h>
 #include <vintf/VintfObject.h>
+
+#include <chrono>
+#include <cmath>
+#include <fstream>
+#include <functional>
+#include <future>
+#include <memory>
+#include <numeric>
+#include <regex>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
 #include "DumpstateInternal.h"
 #include "DumpstateService.h"
-#include "dumpstate.h"
 
 namespace dumpstate_hal_hidl_1_0 = android::hardware::dumpstate::V1_0;
 namespace dumpstate_hal_hidl = android::hardware::dumpstate::V1_1;
@@ -247,7 +248,7 @@
 static const std::string DUMP_HALS_TASK = "DUMP HALS";
 static const std::string DUMP_BOARD_TASK = "dumpstate_board()";
 static const std::string DUMP_CHECKINS_TASK = "DUMP CHECKINS";
-static const std::string POST_PROCESS_UI_TRACES_TASK = "POST-PROCESS UI TRACES";
+static const std::string SERIALIZE_PERFETTO_TRACE_TASK = "SERIALIZE PERFETTO TRACE";
 
 namespace android {
 namespace os {
@@ -1087,11 +1088,11 @@
 
 static void MaybeAddSystemTraceToZip() {
     // This function copies into the .zip the system trace that was snapshotted
-    // by the early call to MaybeSnapshotSystemTrace(), if any background
+    // by the early call to MaybeSnapshotSystemTraceAsync(), if any background
     // tracing was happening.
     bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0;
     if (!system_trace_exists) {
-        // No background trace was happening at the time MaybeSnapshotSystemTrace() was invoked.
+        // No background trace was happening at the time MaybeSnapshotSystemTraceAsync() was invoked
         if (!PropertiesHelper::IsUserBuild()) {
             MYLOGI(
                 "No system traces found. Check for previously uploaded traces by looking for "
@@ -1563,6 +1564,13 @@
     RunDumpsys("DROPBOX SYSTEM SERVER CRASHES", {"dropbox", "-p", "system_server_crash"});
     RunDumpsys("DROPBOX SYSTEM APP CRASHES", {"dropbox", "-p", "system_app_crash"});
 
+
+    printf("========================================================\n");
+    printf("== ANR Traces\n");
+    printf("========================================================\n");
+
+    AddAnrTraceFiles();
+
     printf("========================================================\n");
     printf("== Final progress (pid %d): %d/%d (estimated %d)\n", ds.pid_, ds.progress_->Get(),
            ds.progress_->GetMax(), ds.progress_->GetInitialMax());
@@ -1646,7 +1654,7 @@
 
     // Enqueue slow functions into the thread pool, if the parallel run is enabled.
     std::future<std::string> dump_hals, dump_incident_report, dump_board, dump_checkins,
-            dump_netstats_report, post_process_ui_traces;
+        dump_netstats_report;
     if (ds.dump_pool_) {
         // Pool was shutdown in DumpstateDefaultAfterCritical method in order to
         // drop root user. Restarts it.
@@ -2894,9 +2902,11 @@
                                         int bugreport_flags,
                                         const android::base::unique_fd& bugreport_fd_in,
                                         const android::base::unique_fd& screenshot_fd_in,
-                                        bool is_screenshot_requested) {
+                                        bool is_screenshot_requested,
+                                        bool skip_user_consent) {
     this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA;
     this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT;
+    this->skip_user_consent = skip_user_consent;
     // Duplicate the fds because the passed in fds don't outlive the binder transaction.
     bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0));
     screenshot_fd.reset(fcntl(screenshot_fd_in.get(), F_DUPFD_CLOEXEC, 0));
@@ -2984,46 +2994,52 @@
 }
 
 Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package,
-                                         const bool keep_bugreport_on_retrieval) {
+                                         const bool keep_bugreport_on_retrieval,
+                                         const bool skip_user_consent) {
     Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package,
-                                                    keep_bugreport_on_retrieval);
+                                                    keep_bugreport_on_retrieval,
+                                                    skip_user_consent);
     HandleRunStatus(status);
     return status;
 }
 
 Dumpstate::RunStatus  Dumpstate::RetrieveInternal(int32_t calling_uid,
                                                   const std::string& calling_package,
-                                                  const bool keep_bugreport_on_retrieval) {
-  consent_callback_ = new ConsentCallback();
-  const String16 incidentcompanion("incidentcompanion");
-  sp<android::IBinder> ics(
-      defaultServiceManager()->checkService(incidentcompanion));
-  android::String16 package(calling_package.c_str());
-  if (ics != nullptr) {
-    MYLOGD("Checking user consent via incidentcompanion service\n");
-    android::interface_cast<android::os::IIncidentCompanion>(ics)->authorizeReport(
-        calling_uid, package, String16(), String16(),
-        0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get());
-  } else {
-    MYLOGD(
-        "Unable to check user consent; incidentcompanion service unavailable\n");
-    return RunStatus::USER_CONSENT_TIMED_OUT;
-  }
-  UserConsentResult consent_result = consent_callback_->getResult();
-  int timeout_ms = 30 * 1000;
-  while (consent_result == UserConsentResult::UNAVAILABLE &&
-      consent_callback_->getElapsedTimeMs() < timeout_ms) {
-    sleep(1);
-    consent_result = consent_callback_->getResult();
-  }
-  if (consent_result == UserConsentResult::DENIED) {
-    return RunStatus::USER_CONSENT_DENIED;
-  }
-  if (consent_result == UserConsentResult::UNAVAILABLE) {
-    MYLOGD("Canceling user consent request via incidentcompanion service\n");
-    android::interface_cast<android::os::IIncidentCompanion>(ics)->cancelAuthorization(
-        consent_callback_.get());
-    return RunStatus::USER_CONSENT_TIMED_OUT;
+                                                  const bool keep_bugreport_on_retrieval,
+                                                  const bool skip_user_consent) {
+  if (!android::app::admin::flags::onboarding_consentless_bugreports() || !skip_user_consent) {
+      consent_callback_ = new ConsentCallback();
+      const String16 incidentcompanion("incidentcompanion");
+      sp<android::IBinder> ics(
+          defaultServiceManager()->checkService(incidentcompanion));
+      android::String16 package(calling_package.c_str());
+      if (ics != nullptr) {
+        MYLOGD("Checking user consent via incidentcompanion service\n");
+
+        android::interface_cast<android::os::IIncidentCompanion>(ics)->authorizeReport(
+            calling_uid, package, String16(), String16(),
+            0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get());
+      } else {
+        MYLOGD(
+            "Unable to check user consent; incidentcompanion service unavailable\n");
+        return RunStatus::USER_CONSENT_TIMED_OUT;
+      }
+      UserConsentResult consent_result = consent_callback_->getResult();
+      int timeout_ms = 30 * 1000;
+      while (consent_result == UserConsentResult::UNAVAILABLE &&
+          consent_callback_->getElapsedTimeMs() < timeout_ms) {
+        sleep(1);
+        consent_result = consent_callback_->getResult();
+      }
+      if (consent_result == UserConsentResult::DENIED) {
+        return RunStatus::USER_CONSENT_DENIED;
+      }
+      if (consent_result == UserConsentResult::UNAVAILABLE) {
+        MYLOGD("Canceling user consent request via incidentcompanion service\n");
+        android::interface_cast<android::os::IIncidentCompanion>(ics)->cancelAuthorization(
+            consent_callback_.get());
+        return RunStatus::USER_CONSENT_TIMED_OUT;
+      }
   }
 
   bool copy_succeeded =
@@ -3084,8 +3100,9 @@
 }
 
 void Dumpstate::PreDumpUiData() {
-    MaybeSnapshotSystemTrace();
+    auto snapshot_system_trace = MaybeSnapshotSystemTraceAsync();
     MaybeSnapshotUiTraces();
+    MaybeWaitForSnapshotSystemTrace(std::move(snapshot_system_trace));
 }
 
 /*
@@ -3271,13 +3288,21 @@
     // duration is logged into MYLOG instead.
     PrintHeader();
 
+    bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0;
+    if (options_->use_predumped_ui_data && !system_trace_exists) {
+        MYLOGW("Ignoring 'use predumped data' flag because no predumped data is available");
+        options_->use_predumped_ui_data = false;
+    }
+
+    std::future<std::string> snapshot_system_trace;
+
     bool is_dumpstate_restricted =
         options_->telephony_only || options_->wifi_only || options_->limited_only;
     if (!is_dumpstate_restricted) {
         // Snapshot the system trace now (if running) to avoid that dumpstate's
         // own activity pushes out interesting data from the trace ring buffer.
         // The trace file is added to the zip by MaybeAddSystemTraceToZip().
-        MaybeSnapshotSystemTrace();
+        snapshot_system_trace = MaybeSnapshotSystemTraceAsync();
 
         // Invoke critical dumpsys to preserve system state, before doing anything else.
         RunDumpsysCritical();
@@ -3288,6 +3313,7 @@
     }
 
     MaybeTakeEarlyScreenshot();
+    MaybeWaitForSnapshotSystemTrace(std::move(snapshot_system_trace));
     onUiIntensiveBugreportDumpsFinished(calling_uid);
     MaybeCheckUserConsent(calling_uid, calling_package);
     if (options_->telephony_only) {
@@ -3384,31 +3410,59 @@
     TakeScreenshot();
 }
 
-void Dumpstate::MaybeSnapshotSystemTrace() {
+std::future<std::string> Dumpstate::MaybeSnapshotSystemTraceAsync() {
     // When capturing traces via bugreport handler (BH), this function will be invoked twice:
     // 1) When BH invokes IDumpstate::PreDumpUiData()
     // 2) When BH invokes IDumpstate::startBugreport(flags = BUGREPORT_USE_PREDUMPED_UI_DATA)
     // In this case we don't want to re-invoke perfetto in step 2.
     // In all other standard invocation states, this function is invoked once
     // without the flag BUGREPORT_USE_PREDUMPED_UI_DATA.
+    // This function must run asynchronously to avoid delaying MaybeTakeEarlyScreenshot() in the
+    // standard invocation states (b/316110955).
     if (options_->use_predumped_ui_data) {
-        return;
+        return {};
+    }
+
+    // Create temporary file for the command's output
+    std::string outPath = ds.bugreport_internal_dir_ + "/tmp_serialize_perfetto_trace";
+    auto outFd = android::base::unique_fd(TEMP_FAILURE_RETRY(
+        open(outPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+             S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)));
+    if (outFd < 0) {
+        MYLOGE("Could not open %s to serialize perfetto trace.\n", outPath.c_str());
+        return {};
     }
 
     // If a stale file exists already, remove it.
     unlink(SYSTEM_TRACE_SNAPSHOT);
 
-    // If a background system trace is happening and is marked as "suitable for
-    // bugreport" (i.e. bugreport_score > 0 in the trace config), this command
-    // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
-    // case that no trace is ongoing, this command is a no-op.
-    // Note: this should not be enqueued as we need to freeze the trace before
-    // dumpstate starts. Otherwise the trace ring buffers will contain mostly
-    // the dumpstate's own activity which is irrelevant.
-    RunCommand("SERIALIZE PERFETTO TRACE", {"perfetto", "--save-for-bugreport"},
-               CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build());
-    // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
-    // file in the later stages.
+    MYLOGI("Launching async '%s'", SERIALIZE_PERFETTO_TRACE_TASK.c_str())
+    return std::async(
+        std::launch::async, [this, outPath = std::move(outPath), outFd = std::move(outFd)] {
+            // If a background system trace is happening and is marked as "suitable for
+            // bugreport" (i.e. bugreport_score > 0 in the trace config), this command
+            // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
+            // case that no trace is ongoing, this command is a no-op.
+            // Note: this should not be enqueued as we need to freeze the trace before
+            // dumpstate starts. Otherwise the trace ring buffers will contain mostly
+            // the dumpstate's own activity which is irrelevant.
+            RunCommand(
+                SERIALIZE_PERFETTO_TRACE_TASK, {"perfetto", "--save-for-bugreport"},
+                CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build(),
+                false, outFd);
+            // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
+            // file in the later stages.
+
+            return outPath;
+        });
+}
+
+void Dumpstate::MaybeWaitForSnapshotSystemTrace(std::future<std::string> task) {
+    if (!task.valid()) {
+        return;
+    }
+
+    WaitForTask(std::move(task), SERIALIZE_PERFETTO_TRACE_TASK, STDOUT_FILENO);
 }
 
 void Dumpstate::MaybeSnapshotUiTraces() {
@@ -3416,16 +3470,24 @@
         return;
     }
 
-    const std::vector<std::vector<std::string>> dumpTracesForBugReportCommands = {
-        {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "protolog",
-         "save-for-bugreport"},
-        {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "transitions", "tracing",
-         "save-for-bugreport"},
+    std::vector<std::vector<std::string>> dumpTracesForBugReportCommands = {
         {"cmd", "input_method", "tracing", "save-for-bugreport"},
         {"cmd", "window", "tracing", "save-for-bugreport"},
         {"cmd", "window", "shell", "tracing", "save-for-bugreport"},
     };
 
+    if (!android_tracing_perfetto_transition_tracing()) {
+        dumpTracesForBugReportCommands.push_back({"dumpsys", "activity", "service",
+                                                  "SystemUIService", "WMShell", "transitions",
+                                                  "tracing", "save-for-bugreport"});
+    }
+
+    if (!android_tracing_perfetto_protolog_tracing()) {
+        dumpTracesForBugReportCommands.push_back({"dumpsys", "activity", "service",
+                                                  "SystemUIService", "WMShell", "protolog",
+                                                  "save-for-bugreport"});
+    }
+
     for (const auto& command : dumpTracesForBugReportCommands) {
         RunCommand(
             // Empty name because it's not intended to be classified as a bugreport section.
@@ -3456,7 +3518,9 @@
 
 void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) {
     if (multiuser_get_app_id(calling_uid) == AID_SHELL ||
-        !CalledByApi() || options_->is_consent_deferred) {
+        !CalledByApi() || options_->is_consent_deferred ||
+        (android::app::admin::flags::onboarding_consentless_bugreports() &&
+        options_->skip_user_consent)) {
         // No need to get consent for shell triggered dumpstates, or not
         // through bugreporting API (i.e. no fd to copy back), or when consent
         // is deferred.
@@ -3542,7 +3606,8 @@
     // If the caller has asked to copy the bugreport over to their directory, we need explicit
     // user consent (unless the caller is Shell).
     UserConsentResult consent_result;
-    if (multiuser_get_app_id(calling_uid) == AID_SHELL) {
+    if (multiuser_get_app_id(calling_uid) == AID_SHELL || (options_->skip_user_consent
+    && android::app::admin::flags::onboarding_consentless_bugreports())) {
         consent_result = UserConsentResult::APPROVED;
     } else {
         consent_result = consent_callback_->getResult();
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index de732c0..fcb8cf3 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -364,7 +364,7 @@
      * Initialize() dumpstate before calling this method.
      */
     RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package,
-                        const bool keep_bugreport_on_retrieval);
+                        const bool keep_bugreport_on_retrieval, const bool skip_user_consent);
 
 
 
@@ -412,6 +412,7 @@
         bool do_screenshot = false;
         bool is_screenshot_copied = false;
         bool is_consent_deferred = false;
+        bool skip_user_consent = false;
         bool is_remote_mode = false;
         bool show_header_only = false;
         bool telephony_only = false;
@@ -448,7 +449,8 @@
         void Initialize(BugreportMode bugreport_mode, int bugreport_flags,
                         const android::base::unique_fd& bugreport_fd,
                         const android::base::unique_fd& screenshot_fd,
-                        bool is_screenshot_requested);
+                        bool is_screenshot_requested,
+                        bool skip_user_consent);
 
         /* Returns true if the options set so far are consistent. */
         bool ValidateOptions() const;
@@ -564,13 +566,15 @@
   private:
     RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package);
     RunStatus RetrieveInternal(int32_t calling_uid, const std::string& calling_package,
-                                const bool keep_bugreport_on_retrieval);
+                                const bool keep_bugreport_on_retrieval,
+                                const bool skip_user_consent);
 
     RunStatus DumpstateDefaultAfterCritical();
     RunStatus dumpstate();
 
     void MaybeTakeEarlyScreenshot();
-    void MaybeSnapshotSystemTrace();
+    std::future<std::string> MaybeSnapshotSystemTraceAsync();
+    void MaybeWaitForSnapshotSystemTrace(std::future<std::string> task);
     void MaybeSnapshotUiTraces();
     void MaybeAddUiTracesToZip();
 
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index ccf64fe..a29923a 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -507,7 +507,7 @@
         ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
                                   std::move(screenshot_fd),
                                   Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener,
-                                  true);
+                                  true, false);
     // startBugreport is an async call. Verify binder call succeeded first, then wait till listener
     // gets expected callbacks.
     EXPECT_TRUE(status.isOk());
@@ -545,7 +545,7 @@
     android::binder::Status status =
         ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
                                   std::move(screenshot_fd), 2000,  // invalid bugreport mode
-                                  flags, listener, false);
+                                  flags, listener, false, false);
     EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
 
     // The service should have died, freeing itself up for a new invocation.
@@ -579,7 +579,7 @@
         ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
                                   std::move(screenshot_fd),
                                   Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener1,
-                                  true);
+                                  true, false);
     EXPECT_TRUE(status.isOk());
 
     // try to make another call to startBugreport. This should fail.
@@ -587,7 +587,7 @@
     status = ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd2),
                                         std::move(screenshot_fd2),
                                        Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags,
-                                       listener2, true);
+                                       listener2, true, false);
     EXPECT_FALSE(status.isOk());
     WaitTillExecutionComplete(listener2.get());
     EXPECT_EQ(listener2->getErrorCode(),
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index fc82886..18c2f94 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -16,23 +16,7 @@
 
 #define LOG_TAG "dumpstate_test"
 
-#include "DumpstateInternal.h"
-#include "DumpstateService.h"
-#include "android/os/BnDumpstate.h"
 #include "dumpstate.h"
-#include "DumpPool.h"
-
-#include <gmock/gmock.h>
-#include <gmock/gmock-matchers.h>
-#include <gtest/gtest.h>
-
-#include <fcntl.h>
-#include <libgen.h>
-#include <signal.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <filesystem>
-#include <thread>
 
 #include <aidl/android/hardware/dumpstate/IDumpstateDevice.h>
 #include <android-base/file.h>
@@ -41,10 +25,27 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <android/hardware/dumpstate/1.1/types.h>
+#include <android_tracing.h>
 #include <cutils/log.h>
 #include <cutils/properties.h>
+#include <fcntl.h>
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <libgen.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
 #include <ziparchive/zip_archive.h>
 
+#include <filesystem>
+#include <thread>
+
+#include "DumpPool.h"
+#include "DumpstateInternal.h"
+#include "DumpstateService.h"
+#include "android/os/BnDumpstate.h"
+
 namespace android {
 namespace os {
 namespace dumpstate {
@@ -238,7 +239,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeFullBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true, false);
     EXPECT_TRUE(options_.do_screenshot);
 
     // Other options retain default values
@@ -252,7 +253,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true, false);
     EXPECT_TRUE(options_.do_progress_updates);
     EXPECT_TRUE(options_.do_screenshot);
 
@@ -266,7 +267,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeRemoteBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false, false);
     EXPECT_TRUE(options_.is_remote_mode);
     EXPECT_FALSE(options_.do_vibrate);
     EXPECT_FALSE(options_.do_screenshot);
@@ -280,7 +281,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeWearBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true, false);
     EXPECT_TRUE(options_.do_screenshot);
     EXPECT_TRUE(options_.do_progress_updates);
 
@@ -295,7 +296,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false, false);
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_TRUE(options_.telephony_only);
     EXPECT_TRUE(options_.do_progress_updates);
@@ -310,7 +311,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeWifiBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false, false);
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_TRUE(options_.wifi_only);
 
@@ -490,12 +491,12 @@
     int flags = Dumpstate::BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA |
                 Dumpstate::BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT;
     options_.Initialize(
-      Dumpstate::BugreportMode::BUGREPORT_FULL, flags, fd, fd, true);
+      Dumpstate::BugreportMode::BUGREPORT_FULL, flags, fd, fd, true, false);
     EXPECT_TRUE(options_.is_consent_deferred);
     EXPECT_TRUE(options_.use_predumped_ui_data);
 
     options_.Initialize(
-      Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true);
+      Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true, false);
     EXPECT_FALSE(options_.is_consent_deferred);
     EXPECT_FALSE(options_.use_predumped_ui_data);
 }
@@ -999,10 +1000,13 @@
 
 TEST_F(DumpstateTest, PreDumpUiData) {
     // These traces are always enabled, i.e. they are always pre-dumped
-    const std::vector<std::filesystem::path> uiTraces = {
-        std::filesystem::path{"/data/misc/wmtrace/wm_transition_trace.winscope"},
-        std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"},
-    };
+    std::vector<std::filesystem::path> uiTraces;
+    if (!android_tracing_perfetto_transition_tracing()) {
+        uiTraces.push_back(
+            std::filesystem::path{"/data/misc/wmtrace/wm_transition_trace.winscope"});
+        uiTraces.push_back(
+            std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"});
+    }
 
     for (const auto traceFile : uiTraces) {
         std::system(("rm -f " + traceFile.string()).c_str());
diff --git a/cmds/evemu-record/README.md b/cmds/evemu-record/README.md
new file mode 100644
index 0000000..5d16d51
--- /dev/null
+++ b/cmds/evemu-record/README.md
@@ -0,0 +1,48 @@
+# `evemu-record`
+
+This is a Rust implementation of the `evemu-record` command from the [FreeDesktop project's evemu
+suite][FreeDesktop]. It records the descriptor and events produced by a single input device in a
+[simple text-based format][format] that can be replayed using the [`uinput` command on
+Android][uinput] or the FreeDesktop evemu tools on other Linux-based platforms. It is included by
+default with `userdebug` and `eng` builds of Android.
+
+The command-line interface is the same as that of the FreeDesktop version, except for
+Android-specific features. For usage instructions, run `evemu-record --help`.
+
+## Usage example
+
+From a computer connected to the device over ADB, you can start a recording:
+
+```
+$ adb shell evemu-record > my-recording.evemu
+Available devices:
+/dev/input/event0:      gpio_keys
+/dev/input/event1:      s2mpg12-power-keys
+/dev/input/event2:      NVTCapacitiveTouchScreen
+/dev/input/event3:      NVTCapacitivePen
+/dev/input/event4:      uinput-folio
+/dev/input/event5:      ACME Touchpad
+Select the device event number [0-5]: 5
+```
+
+...then use the input device for a while, and press Ctrl+C to finish. You will now have a
+`my-recording.evemu` file that you can examine in a text editor. To replay it, use the [`uinput`
+command][uinput]:
+
+```
+$ adb shell uinput - < my-recording.evemu
+```
+
+## Android-specific features
+
+### Timestamp bases
+
+By default, event timestamps are recorded relative to the time of the first event received during
+the recording. Passing `--timestamp-base=boot` causes the timestamps to be recorded relative to the
+system boot time instead. While this does not affect the playback of the recording, it can be useful
+for matching recorded events with other logs that use such timestamps, such as `dmesg` or the
+touchpad gesture debug logs emitted by `TouchpadInputMapper`.
+
+[FreeDesktop]: https://gitlab.freedesktop.org/libevdev/evemu
+[format]: https://gitlab.freedesktop.org/libevdev/evemu#device-description-format
+[uinput]: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/cmds/uinput/README.md
diff --git a/cmds/sfdo/Android.bp b/cmds/sfdo/Android.bp
index c19c9da..a91a7dc 100644
--- a/cmds/sfdo/Android.bp
+++ b/cmds/sfdo/Android.bp
@@ -1,17 +1,10 @@
-cc_binary {
+rust_binary {
     name: "sfdo",
+    srcs: ["sfdo.rs"],
 
-    srcs: ["sfdo.cpp"],
-
-    shared_libs: [
-        "libutils",
-        "libgui",
+    rustlibs: [
+        "android.gui-rust",
+        "libclap",
     ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wunused",
-        "-Wunreachable-code",
-    ],
+    edition: "2021",
 }
diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp
deleted file mode 100644
index de0e171..0000000
--- a/cmds/sfdo/sfdo.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <inttypes.h>
-#include <stdint.h>
-#include <any>
-#include <map>
-
-#include <cutils/properties.h>
-#include <sys/resource.h>
-#include <utils/Log.h>
-
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
-#include <gui/SurfaceControl.h>
-#include <private/gui/ComposerServiceAIDL.h>
-
-using namespace android;
-
-std::map<std::string, std::any> g_functions;
-
-enum class ParseToggleResult {
-    kError,
-    kFalse,
-    kTrue,
-};
-
-const std::map<std::string, std::string> g_function_details = {
-        {"debugFlash", "[optional(delay)] Perform a debug flash."},
-        {"frameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
-        {"scheduleComposite", "Force composite ahead of next VSYNC."},
-        {"scheduleCommit", "Force commit ahead of next VSYNC."},
-        {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
-        {"forceClientComposition",
-         "[enabled | disabled] When enabled, it disables "
-         "Hardware Overlays, and routes all window composition to the GPU. This can "
-         "help check if there is a bug in HW Composer."},
-};
-
-static void ShowUsage() {
-    std::cout << "usage: sfdo [help, frameRateIndicator show, debugFlash enabled, ...]\n\n";
-    for (const auto& sf : g_functions) {
-        const std::string fn = sf.first;
-        std::string fdetails = "TODO";
-        if (g_function_details.find(fn) != g_function_details.end())
-            fdetails = g_function_details.find(fn)->second;
-        std::cout << "    " << fn << ": " << fdetails << "\n";
-    }
-}
-
-// Returns 1 for positive keywords and 0 for negative keywords.
-// If the string does not match any it will return -1.
-ParseToggleResult parseToggle(const char* str) {
-    const std::unordered_set<std::string> positive{"1",  "true",    "y",   "yes",
-                                                   "on", "enabled", "show"};
-    const std::unordered_set<std::string> negative{"0",   "false",    "n",   "no",
-                                                   "off", "disabled", "hide"};
-
-    const std::string word(str);
-    if (positive.count(word)) {
-        return ParseToggleResult::kTrue;
-    }
-    if (negative.count(word)) {
-        return ParseToggleResult::kFalse;
-    }
-
-    return ParseToggleResult::kError;
-}
-
-int frameRateIndicator(int argc, char** argv) {
-    bool hide = false, show = false;
-    if (argc == 3) {
-        show = strcmp(argv[2], "show") == 0;
-        hide = strcmp(argv[2], "hide") == 0;
-    }
-
-    if (show || hide) {
-        ComposerServiceAIDL::getComposerService()->enableRefreshRateOverlay(show);
-    } else {
-        std::cerr << "Incorrect usage of frameRateIndicator. Missing [hide | show].\n";
-        return -1;
-    }
-    return 0;
-}
-
-int debugFlash(int argc, char** argv) {
-    int delay = 0;
-    if (argc == 3) {
-        delay = atoi(argv[2]) == 0;
-    }
-
-    ComposerServiceAIDL::getComposerService()->setDebugFlash(delay);
-    return 0;
-}
-
-int scheduleComposite([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    ComposerServiceAIDL::getComposerService()->scheduleComposite();
-    return 0;
-}
-
-int scheduleCommit([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    ComposerServiceAIDL::getComposerService()->scheduleCommit();
-    return 0;
-}
-
-int forceClientComposition(int argc, char** argv) {
-    bool enabled = true;
-    // A valid command looks like this:
-    // adb shell sfdo forceClientComposition enabled
-    if (argc >= 3) {
-        const ParseToggleResult toggle = parseToggle(argv[2]);
-        if (toggle == ParseToggleResult::kError) {
-            std::cerr << "Incorrect usage of forceClientComposition. "
-                         "Missing [enabled | disabled].\n";
-            return -1;
-        }
-        if (argc > 3) {
-            std::cerr << "Too many arguments after [enabled | disabled]. "
-                         "Ignoring extra arguments.\n";
-        }
-        enabled = (toggle == ParseToggleResult::kTrue);
-    } else {
-        std::cerr << "Incorrect usage of forceClientComposition. Missing [enabled | disabled].\n";
-        return -1;
-    }
-
-    ComposerServiceAIDL::getComposerService()->forceClientComposition(enabled);
-    return 0;
-}
-
-int main(int argc, char** argv) {
-    std::cout << "Execute SurfaceFlinger internal commands.\n";
-    std::cout << "sfdo requires to be run with root permissions..\n";
-
-    g_functions["frameRateIndicator"] = frameRateIndicator;
-    g_functions["debugFlash"] = debugFlash;
-    g_functions["scheduleComposite"] = scheduleComposite;
-    g_functions["scheduleCommit"] = scheduleCommit;
-    g_functions["forceClientComposition"] = forceClientComposition;
-
-    if (argc > 1 && g_functions.find(argv[1]) != g_functions.end()) {
-        std::cout << "Running: " << argv[1] << "\n";
-        const std::string key(argv[1]);
-        const auto fn = g_functions[key];
-        int result = std::any_cast<int (*)(int, char**)>(fn)(argc, argv);
-        if (result == 0) {
-            std::cout << "Success.\n";
-        }
-        return result;
-    } else {
-        ShowUsage();
-    }
-    return 0;
-}
\ No newline at end of file
diff --git a/cmds/sfdo/sfdo.rs b/cmds/sfdo/sfdo.rs
new file mode 100644
index 0000000..863df6b
--- /dev/null
+++ b/cmds/sfdo/sfdo.rs
@@ -0,0 +1,155 @@
+// Copyright (C) 2024 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.
+
+//! sfdo: Make surface flinger do things
+use android_gui::{aidl::android::gui::ISurfaceComposer::ISurfaceComposer, binder};
+use clap::{Parser, Subcommand};
+use std::fmt::Debug;
+
+const SERVICE_IDENTIFIER: &str = "SurfaceFlingerAIDL";
+
+fn print_result<T, E>(function_name: &str, res: Result<T, E>)
+where
+    E: Debug,
+{
+    match res {
+        Ok(_) => println!("{}: Operation successful!", function_name),
+        Err(err) => println!("{}: Operation failed: {:?}", function_name, err),
+    }
+}
+
+fn parse_toggle(toggle_value: &str) -> Option<bool> {
+    let positive = ["1", "true", "y", "yes", "on", "enabled", "show"];
+    let negative = ["0", "false", "n", "no", "off", "disabled", "hide"];
+
+    let word = toggle_value.to_lowercase(); // Case-insensitive comparison
+
+    if positive.contains(&word.as_str()) {
+        Some(true)
+    } else if negative.contains(&word.as_str()) {
+        Some(false)
+    } else {
+        None
+    }
+}
+
+#[derive(Parser)]
+#[command(version = "0.1", about = "Execute SurfaceFlinger internal commands.")]
+#[command(propagate_version = true)]
+struct Cli {
+    #[command(subcommand)]
+    command: Option<Commands>,
+}
+
+#[derive(Subcommand, Debug)]
+enum Commands {
+    #[command(about = "[optional(--delay)] Perform a debug flash.")]
+    DebugFlash {
+        #[arg(short, long, default_value_t = 0)]
+        delay: i32,
+    },
+
+    #[command(
+        about = "state = [enabled | disabled] When enabled, it disables Hardware Overlays, \
+                      and routes all window composition to the GPU. This can help check if \
+                      there is a bug in HW Composer."
+    )]
+    ForceClientComposition { state: Option<String> },
+
+    #[command(about = "state = [hide | show], displays the framerate in the top left corner.")]
+    FrameRateIndicator { state: Option<String> },
+
+    #[command(about = "Force composite ahead of next VSYNC.")]
+    ScheduleComposite,
+
+    #[command(about = "Force commit ahead of next VSYNC.")]
+    ScheduleCommit,
+}
+
+/// sfdo command line tool
+///
+/// sfdo allows you to call different functions from the SurfaceComposer using
+/// the adb shell.
+fn main() {
+    binder::ProcessState::start_thread_pool();
+    let composer_service = match binder::get_interface::<dyn ISurfaceComposer>(SERVICE_IDENTIFIER) {
+        Ok(service) => service,
+        Err(err) => {
+            eprintln!("Unable to connect to ISurfaceComposer: {}", err);
+            return;
+        }
+    };
+
+    let cli = Cli::parse();
+
+    match &cli.command {
+        Some(Commands::FrameRateIndicator { state }) => {
+            if let Some(op_state) = state {
+                let toggle = parse_toggle(op_state);
+                match toggle {
+                    Some(true) => {
+                        let res = composer_service.enableRefreshRateOverlay(true);
+                        print_result("enableRefreshRateOverlay", res);
+                    }
+                    Some(false) => {
+                        let res = composer_service.enableRefreshRateOverlay(false);
+                        print_result("enableRefreshRateOverlay", res);
+                    }
+                    None => {
+                        eprintln!("Invalid state: {}, choices are [hide | show]", op_state);
+                    }
+                }
+            } else {
+                eprintln!("No state, choices are [hide | show]");
+            }
+        }
+        Some(Commands::DebugFlash { delay }) => {
+            let res = composer_service.setDebugFlash(*delay);
+            print_result("setDebugFlash", res);
+        }
+        Some(Commands::ScheduleComposite) => {
+            let res = composer_service.scheduleComposite();
+            print_result("scheduleComposite", res);
+        }
+        Some(Commands::ScheduleCommit) => {
+            let res = composer_service.scheduleCommit();
+            print_result("scheduleCommit", res);
+        }
+        Some(Commands::ForceClientComposition { state }) => {
+            if let Some(op_state) = state {
+                let toggle = parse_toggle(op_state);
+                match toggle {
+                    Some(true) => {
+                        let res = composer_service.forceClientComposition(true);
+                        print_result("forceClientComposition", res);
+                    }
+                    Some(false) => {
+                        let res = composer_service.forceClientComposition(false);
+                        print_result("forceClientComposition", res);
+                    }
+                    None => {
+                        eprintln!("Invalid state: {}, choices are [enabled | disabled]", op_state);
+                    }
+                }
+            } else {
+                eprintln!("No state, choices are [enabled | disabled]");
+            }
+        }
+        None => {
+            println!("Execute SurfaceFlinger internal commands.");
+            println!("run `adb shell sfdo help` for more to view the commands.");
+            println!("run `adb shell sfdo [COMMAND] --help` for more info on the command.");
+        }
+    }
+}
diff --git a/cmds/surfacereplayer/replayer/Replayer.cpp b/cmds/surfacereplayer/replayer/Replayer.cpp
deleted file mode 100644
index 44235cc..0000000
--- a/cmds/surfacereplayer/replayer/Replayer.cpp
+++ /dev/null
@@ -1,707 +0,0 @@
-/* Copyright 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SurfaceReplayer"
-
-#include "Replayer.h"
-
-#include <android/native_window.h>
-
-#include <android-base/file.h>
-
-#include <gui/BufferQueue.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/LayerState.h>
-#include <gui/Surface.h>
-#include <private/gui/ComposerService.h>
-
-#include <utils/Log.h>
-#include <utils/String8.h>
-#include <utils/Trace.h>
-
-#include <chrono>
-#include <cmath>
-#include <condition_variable>
-#include <cstdlib>
-#include <fstream>
-#include <functional>
-#include <iostream>
-#include <mutex>
-#include <sstream>
-#include <string>
-#include <thread>
-#include <vector>
-
-using namespace android;
-
-std::atomic_bool Replayer::sReplayingManually(false);
-
-Replayer::Replayer(const std::string& filename, bool replayManually, int numThreads, bool wait,
-        nsecs_t stopHere)
-      : mTrace(),
-        mLoaded(false),
-        mIncrementIndex(0),
-        mCurrentTime(0),
-        mNumThreads(numThreads),
-        mWaitForTimeStamps(wait),
-        mStopTimeStamp(stopHere) {
-    srand(RAND_COLOR_SEED);
-
-    std::string input;
-    if (!android::base::ReadFileToString(filename, &input, true)) {
-        std::cerr << "Trace did not load. Does " << filename << " exist?" << std::endl;
-        abort();
-    }
-
-    mLoaded = mTrace.ParseFromString(input);
-    if (!mLoaded) {
-        std::cerr << "Trace did not load." << std::endl;
-        abort();
-    }
-
-    mCurrentTime = mTrace.increment(0).time_stamp();
-
-    sReplayingManually.store(replayManually);
-
-    if (stopHere < 0) {
-        mHasStopped = true;
-    }
-}
-
-Replayer::Replayer(const Trace& t, bool replayManually, int numThreads, bool wait, nsecs_t stopHere)
-      : mTrace(t),
-        mLoaded(true),
-        mIncrementIndex(0),
-        mCurrentTime(0),
-        mNumThreads(numThreads),
-        mWaitForTimeStamps(wait),
-        mStopTimeStamp(stopHere) {
-    srand(RAND_COLOR_SEED);
-    mCurrentTime = mTrace.increment(0).time_stamp();
-
-    sReplayingManually.store(replayManually);
-
-    if (stopHere < 0) {
-        mHasStopped = true;
-    }
-}
-
-status_t Replayer::replay() {
-    signal(SIGINT, Replayer::stopAutoReplayHandler); //for manual control
-
-    ALOGV("There are %d increments.", mTrace.increment_size());
-
-    status_t status = loadSurfaceComposerClient();
-
-    if (status != NO_ERROR) {
-        ALOGE("Couldn't create SurfaceComposerClient (%d)", status);
-        return status;
-    }
-
-    SurfaceComposerClient::enableVSyncInjections(true);
-
-    initReplay();
-
-    ALOGV("Starting actual Replay!");
-    while (!mPendingIncrements.empty()) {
-        mCurrentIncrement = mTrace.increment(mIncrementIndex);
-
-        if (mHasStopped == false && mCurrentIncrement.time_stamp() >= mStopTimeStamp) {
-            mHasStopped = true;
-            sReplayingManually.store(true);
-        }
-
-        waitForConsoleCommmand();
-
-        if (mWaitForTimeStamps) {
-            waitUntilTimestamp(mCurrentIncrement.time_stamp());
-        }
-
-        auto event = mPendingIncrements.front();
-        mPendingIncrements.pop();
-
-        event->complete();
-
-        if (event->getIncrementType() == Increment::kVsyncEvent) {
-            mWaitingForNextVSync = false;
-        }
-
-        if (mIncrementIndex + mNumThreads < mTrace.increment_size()) {
-            status = dispatchEvent(mIncrementIndex + mNumThreads);
-
-            if (status != NO_ERROR) {
-                SurfaceComposerClient::enableVSyncInjections(false);
-                return status;
-            }
-        }
-
-        mIncrementIndex++;
-        mCurrentTime = mCurrentIncrement.time_stamp();
-    }
-
-    SurfaceComposerClient::enableVSyncInjections(false);
-
-    return status;
-}
-
-status_t Replayer::initReplay() {
-    for (int i = 0; i < mNumThreads && i < mTrace.increment_size(); i++) {
-        status_t status = dispatchEvent(i);
-
-        if (status != NO_ERROR) {
-            ALOGE("Unable to dispatch event (%d)", status);
-            return status;
-        }
-    }
-
-    return NO_ERROR;
-}
-
-void Replayer::stopAutoReplayHandler(int /*signal*/) {
-    if (sReplayingManually) {
-        SurfaceComposerClient::enableVSyncInjections(false);
-        exit(0);
-    }
-
-    sReplayingManually.store(true);
-}
-
-std::vector<std::string> split(const std::string& s, const char delim) {
-    std::vector<std::string> elems;
-    std::stringstream ss(s);
-    std::string item;
-    while (getline(ss, item, delim)) {
-        elems.push_back(item);
-    }
-    return elems;
-}
-
-bool isNumber(const std::string& s) {
-    return !s.empty() &&
-           std::find_if(s.begin(), s.end(), [](char c) { return !std::isdigit(c); }) == s.end();
-}
-
-void Replayer::waitForConsoleCommmand() {
-    if (!sReplayingManually || mWaitingForNextVSync) {
-        return;
-    }
-
-    while (true) {
-        std::string input = "";
-        std::cout << "> ";
-        getline(std::cin, input);
-
-        if (input.empty()) {
-            input = mLastInput;
-        } else {
-            mLastInput = input;
-        }
-
-        if (mLastInput.empty()) {
-            continue;
-        }
-
-        std::vector<std::string> inputs = split(input, ' ');
-
-        if (inputs[0] == "n") {  // next vsync
-            mWaitingForNextVSync = true;
-            break;
-
-        } else if (inputs[0] == "ni") {  // next increment
-            break;
-
-        } else if (inputs[0] == "c") {  // continue
-            if (inputs.size() > 1 && isNumber(inputs[1])) {
-                long milliseconds = stoi(inputs[1]);
-                std::thread([&] {
-                    std::cout << "Started!" << std::endl;
-                    std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
-                    sReplayingManually.store(true);
-                    std::cout << "Should have stopped!" << std::endl;
-                }).detach();
-            }
-            sReplayingManually.store(false);
-            mWaitingForNextVSync = false;
-            break;
-
-        } else if (inputs[0] == "s") {  // stop at this timestamp
-            if (inputs.size() < 1) {
-                std::cout << "No time stamp given" << std::endl;
-                continue;
-            }
-            sReplayingManually.store(false);
-            mStopTimeStamp = stol(inputs[1]);
-            mHasStopped = false;
-            break;
-        } else if (inputs[0] == "l") {  // list
-            std::cout << "Time stamp: " << mCurrentIncrement.time_stamp() << "\n";
-            continue;
-        } else if (inputs[0] == "q") {  // quit
-            SurfaceComposerClient::enableVSyncInjections(false);
-            exit(0);
-
-        } else if (inputs[0] == "h") {  // help
-                                        // add help menu
-            std::cout << "Manual Replay options:\n";
-            std::cout << " n  - Go to next VSync\n";
-            std::cout << " ni - Go to next increment\n";
-            std::cout << " c  - Continue\n";
-            std::cout << " c [milliseconds] - Continue until specified number of milliseconds\n";
-            std::cout << " s [timestamp]    - Continue and stop at specified timestamp\n";
-            std::cout << " l  - List out timestamp of current increment\n";
-            std::cout << " h  - Display help menu\n";
-            std::cout << std::endl;
-            continue;
-        }
-
-        std::cout << "Invalid Command" << std::endl;
-    }
-}
-
-status_t Replayer::dispatchEvent(int index) {
-    auto increment = mTrace.increment(index);
-    std::shared_ptr<Event> event = std::make_shared<Event>(increment.increment_case());
-    mPendingIncrements.push(event);
-
-    status_t status = NO_ERROR;
-    switch (increment.increment_case()) {
-        case increment.kTransaction: {
-            std::thread(&Replayer::doTransaction, this, increment.transaction(), event).detach();
-        } break;
-        case increment.kSurfaceCreation: {
-            std::thread(&Replayer::createSurfaceControl, this, increment.surface_creation(), event)
-                    .detach();
-        } break;
-        case increment.kBufferUpdate: {
-            std::lock_guard<std::mutex> lock1(mLayerLock);
-            std::lock_guard<std::mutex> lock2(mBufferQueueSchedulerLock);
-
-            Dimensions dimensions(increment.buffer_update().w(), increment.buffer_update().h());
-            BufferEvent bufferEvent(event, dimensions);
-
-            auto layerId = increment.buffer_update().id();
-            if (mBufferQueueSchedulers.count(layerId) == 0) {
-                mBufferQueueSchedulers[layerId] = std::make_shared<BufferQueueScheduler>(
-                        mLayers[layerId], mColors[layerId], layerId);
-                mBufferQueueSchedulers[layerId]->addEvent(bufferEvent);
-
-                std::thread(&BufferQueueScheduler::startScheduling,
-                        mBufferQueueSchedulers[increment.buffer_update().id()].get())
-                        .detach();
-            } else {
-                auto bqs = mBufferQueueSchedulers[increment.buffer_update().id()];
-                bqs->addEvent(bufferEvent);
-            }
-        } break;
-        case increment.kVsyncEvent: {
-            std::thread(&Replayer::injectVSyncEvent, this, increment.vsync_event(), event).detach();
-        } break;
-        case increment.kDisplayCreation: {
-            std::thread(&Replayer::createDisplay, this, increment.display_creation(), event)
-                    .detach();
-        } break;
-        case increment.kDisplayDeletion: {
-            std::thread(&Replayer::deleteDisplay, this, increment.display_deletion(), event)
-                    .detach();
-        } break;
-        case increment.kPowerModeUpdate: {
-            std::thread(&Replayer::updatePowerMode, this, increment.power_mode_update(), event)
-                    .detach();
-        } break;
-        default:
-            ALOGE("Unknown Increment Type: %d", increment.increment_case());
-            status = BAD_VALUE;
-            break;
-    }
-
-    return status;
-}
-
-status_t Replayer::doTransaction(const Transaction& t, const std::shared_ptr<Event>& event) {
-    ALOGV("Started Transaction");
-
-    SurfaceComposerClient::Transaction liveTransaction;
-
-    status_t status = NO_ERROR;
-
-    status = doSurfaceTransaction(liveTransaction, t.surface_change());
-    doDisplayTransaction(liveTransaction, t.display_change());
-
-    if (t.animation()) {
-        liveTransaction.setAnimationTransaction();
-    }
-
-    event->readyToExecute();
-
-    liveTransaction.apply(t.synchronous());
-
-    ALOGV("Ended Transaction");
-
-    return status;
-}
-
-status_t Replayer::doSurfaceTransaction(
-        SurfaceComposerClient::Transaction& transaction,
-        const SurfaceChanges& surfaceChanges) {
-    status_t status = NO_ERROR;
-
-    for (const SurfaceChange& change : surfaceChanges) {
-        std::unique_lock<std::mutex> lock(mLayerLock);
-        if (mLayers[change.id()] == nullptr) {
-            mLayerCond.wait(lock, [&] { return (mLayers[change.id()] != nullptr); });
-        }
-
-        switch (change.SurfaceChange_case()) {
-            case SurfaceChange::SurfaceChangeCase::kPosition:
-                setPosition(transaction, change.id(), change.position());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kSize:
-                setSize(transaction, change.id(), change.size());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kAlpha:
-                setAlpha(transaction, change.id(), change.alpha());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kLayer:
-                setLayer(transaction, change.id(), change.layer());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kCrop:
-                setCrop(transaction, change.id(), change.crop());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kCornerRadius:
-                setCornerRadius(transaction, change.id(), change.corner_radius());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kMatrix:
-                setMatrix(transaction, change.id(), change.matrix());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kTransparentRegionHint:
-                setTransparentRegionHint(transaction, change.id(),
-                        change.transparent_region_hint());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kLayerStack:
-                setLayerStack(transaction, change.id(), change.layer_stack());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kHiddenFlag:
-                setHiddenFlag(transaction, change.id(), change.hidden_flag());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kOpaqueFlag:
-                setOpaqueFlag(transaction, change.id(), change.opaque_flag());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kSecureFlag:
-                setSecureFlag(transaction, change.id(), change.secure_flag());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kReparent:
-                setReparentChange(transaction, change.id(), change.reparent());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kRelativeParent:
-                setRelativeParentChange(transaction, change.id(), change.relative_parent());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kShadowRadius:
-                setShadowRadiusChange(transaction, change.id(), change.shadow_radius());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kBlurRegions:
-                setBlurRegionsChange(transaction, change.id(), change.blur_regions());
-                break;
-            default:
-                status = 1;
-                break;
-        }
-
-        if (status != NO_ERROR) {
-            ALOGE("Unknown Transaction Code");
-            return status;
-        }
-    }
-    return status;
-}
-
-void Replayer::doDisplayTransaction(SurfaceComposerClient::Transaction& t,
-        const DisplayChanges& displayChanges) {
-    for (const DisplayChange& change : displayChanges) {
-        ALOGV("Doing display transaction");
-        std::unique_lock<std::mutex> lock(mDisplayLock);
-        if (mDisplays[change.id()] == nullptr) {
-            mDisplayCond.wait(lock, [&] { return (mDisplays[change.id()] != nullptr); });
-        }
-
-        switch (change.DisplayChange_case()) {
-            case DisplayChange::DisplayChangeCase::kSurface:
-                setDisplaySurface(t, change.id(), change.surface());
-                break;
-            case DisplayChange::DisplayChangeCase::kLayerStack:
-                setDisplayLayerStack(t, change.id(), change.layer_stack());
-                break;
-            case DisplayChange::DisplayChangeCase::kSize:
-                setDisplaySize(t, change.id(), change.size());
-                break;
-            case DisplayChange::DisplayChangeCase::kProjection:
-                setDisplayProjection(t, change.id(), change.projection());
-                break;
-            default:
-                break;
-        }
-    }
-}
-
-void Replayer::setPosition(SurfaceComposerClient::Transaction& t,
-        layer_id id, const PositionChange& pc) {
-    ALOGV("Layer %d: Setting Position -- x=%f, y=%f", id, pc.x(), pc.y());
-    t.setPosition(mLayers[id], pc.x(), pc.y());
-}
-
-void Replayer::setSize(SurfaceComposerClient::Transaction& t,
-        layer_id id, const SizeChange& sc) {
-    ALOGV("Layer %d: Setting Size -- w=%u, h=%u", id, sc.w(), sc.h());
-}
-
-void Replayer::setLayer(SurfaceComposerClient::Transaction& t,
-        layer_id id, const LayerChange& lc) {
-    ALOGV("Layer %d: Setting Layer -- layer=%d", id, lc.layer());
-    t.setLayer(mLayers[id], lc.layer());
-}
-
-void Replayer::setAlpha(SurfaceComposerClient::Transaction& t,
-        layer_id id, const AlphaChange& ac) {
-    ALOGV("Layer %d: Setting Alpha -- alpha=%f", id, ac.alpha());
-    t.setAlpha(mLayers[id], ac.alpha());
-}
-
-void Replayer::setCrop(SurfaceComposerClient::Transaction& t,
-        layer_id id, const CropChange& cc) {
-    ALOGV("Layer %d: Setting Crop -- left=%d, top=%d, right=%d, bottom=%d", id,
-            cc.rectangle().left(), cc.rectangle().top(), cc.rectangle().right(),
-            cc.rectangle().bottom());
-
-    Rect r = Rect(cc.rectangle().left(), cc.rectangle().top(), cc.rectangle().right(),
-            cc.rectangle().bottom());
-    t.setCrop(mLayers[id], r);
-}
-
-void Replayer::setCornerRadius(SurfaceComposerClient::Transaction& t,
-        layer_id id, const CornerRadiusChange& cc) {
-    ALOGV("Layer %d: Setting Corner Radius -- cornerRadius=%d", id, cc.corner_radius());
-
-    t.setCornerRadius(mLayers[id], cc.corner_radius());
-}
-
-void Replayer::setBackgroundBlurRadius(SurfaceComposerClient::Transaction& t,
-        layer_id id, const BackgroundBlurRadiusChange& cc) {
-    ALOGV("Layer %d: Setting Background Blur Radius -- backgroundBlurRadius=%d", id,
-        cc.background_blur_radius());
-
-    t.setBackgroundBlurRadius(mLayers[id], cc.background_blur_radius());
-}
-
-void Replayer::setMatrix(SurfaceComposerClient::Transaction& t,
-        layer_id id, const MatrixChange& mc) {
-    ALOGV("Layer %d: Setting Matrix -- dsdx=%f, dtdx=%f, dsdy=%f, dtdy=%f", id, mc.dsdx(),
-            mc.dtdx(), mc.dsdy(), mc.dtdy());
-    t.setMatrix(mLayers[id], mc.dsdx(), mc.dtdx(), mc.dsdy(), mc.dtdy());
-}
-
-void Replayer::setTransparentRegionHint(SurfaceComposerClient::Transaction& t,
-        layer_id id, const TransparentRegionHintChange& trhc) {
-    ALOGV("Setting Transparent Region Hint");
-    Region re = Region();
-
-    for (const auto& r : trhc.region()) {
-        Rect rect = Rect(r.left(), r.top(), r.right(), r.bottom());
-        re.merge(rect);
-    }
-
-    t.setTransparentRegionHint(mLayers[id], re);
-}
-
-void Replayer::setLayerStack(SurfaceComposerClient::Transaction& t,
-        layer_id id, const LayerStackChange& lsc) {
-    ALOGV("Layer %d: Setting LayerStack -- layer_stack=%d", id, lsc.layer_stack());
-    t.setLayerStack(mLayers[id], ui::LayerStack::fromValue(lsc.layer_stack()));
-}
-
-void Replayer::setHiddenFlag(SurfaceComposerClient::Transaction& t,
-        layer_id id, const HiddenFlagChange& hfc) {
-    ALOGV("Layer %d: Setting Hidden Flag -- hidden_flag=%d", id, hfc.hidden_flag());
-    layer_id flag = hfc.hidden_flag() ? layer_state_t::eLayerHidden : 0;
-
-    t.setFlags(mLayers[id], flag, layer_state_t::eLayerHidden);
-}
-
-void Replayer::setOpaqueFlag(SurfaceComposerClient::Transaction& t,
-        layer_id id, const OpaqueFlagChange& ofc) {
-    ALOGV("Layer %d: Setting Opaque Flag -- opaque_flag=%d", id, ofc.opaque_flag());
-    layer_id flag = ofc.opaque_flag() ? layer_state_t::eLayerOpaque : 0;
-
-    t.setFlags(mLayers[id], flag, layer_state_t::eLayerOpaque);
-}
-
-void Replayer::setSecureFlag(SurfaceComposerClient::Transaction& t,
-        layer_id id, const SecureFlagChange& sfc) {
-    ALOGV("Layer %d: Setting Secure Flag -- secure_flag=%d", id, sfc.secure_flag());
-    layer_id flag = sfc.secure_flag() ? layer_state_t::eLayerSecure : 0;
-
-    t.setFlags(mLayers[id], flag, layer_state_t::eLayerSecure);
-}
-
-void Replayer::setDisplaySurface(SurfaceComposerClient::Transaction& t,
-        display_id id, const DispSurfaceChange& /*dsc*/) {
-    sp<IGraphicBufferProducer> outProducer;
-    sp<IGraphicBufferConsumer> outConsumer;
-    BufferQueue::createBufferQueue(&outProducer, &outConsumer);
-
-    t.setDisplaySurface(mDisplays[id], outProducer);
-}
-
-void Replayer::setDisplayLayerStack(SurfaceComposerClient::Transaction& t,
-        display_id id, const LayerStackChange& lsc) {
-    t.setDisplayLayerStack(mDisplays[id], ui::LayerStack::fromValue(lsc.layer_stack()));
-}
-
-void Replayer::setDisplaySize(SurfaceComposerClient::Transaction& t,
-        display_id id, const SizeChange& sc) {
-    t.setDisplaySize(mDisplays[id], sc.w(), sc.h());
-}
-
-void Replayer::setDisplayProjection(SurfaceComposerClient::Transaction& t,
-        display_id id, const ProjectionChange& pc) {
-    Rect viewport = Rect(pc.viewport().left(), pc.viewport().top(), pc.viewport().right(),
-            pc.viewport().bottom());
-    Rect frame = Rect(pc.frame().left(), pc.frame().top(), pc.frame().right(), pc.frame().bottom());
-
-    t.setDisplayProjection(mDisplays[id], ui::toRotation(pc.orientation()), viewport, frame);
-}
-
-status_t Replayer::createSurfaceControl(
-        const SurfaceCreation& create, const std::shared_ptr<Event>& event) {
-    event->readyToExecute();
-
-    ALOGV("Creating Surface Control: ID: %d", create.id());
-    sp<SurfaceControl> surfaceControl = mComposerClient->createSurface(
-            String8(create.name().c_str()), create.w(), create.h(), PIXEL_FORMAT_RGBA_8888, 0);
-
-    if (surfaceControl == nullptr) {
-        ALOGE("CreateSurfaceControl: unable to create surface control");
-        return BAD_VALUE;
-    }
-
-    std::lock_guard<std::mutex> lock1(mLayerLock);
-    auto& layer = mLayers[create.id()];
-    layer = surfaceControl;
-
-    mColors[create.id()] = HSV(rand() % 360, 1, 1);
-
-    mLayerCond.notify_all();
-
-    std::lock_guard<std::mutex> lock2(mBufferQueueSchedulerLock);
-    if (mBufferQueueSchedulers.count(create.id()) != 0) {
-        mBufferQueueSchedulers[create.id()]->setSurfaceControl(
-                mLayers[create.id()], mColors[create.id()]);
-    }
-
-    return NO_ERROR;
-}
-
-status_t Replayer::injectVSyncEvent(
-        const VSyncEvent& vSyncEvent, const std::shared_ptr<Event>& event) {
-    ALOGV("Injecting VSync Event");
-
-    event->readyToExecute();
-
-    SurfaceComposerClient::injectVSync(vSyncEvent.when());
-
-    return NO_ERROR;
-}
-
-void Replayer::createDisplay(const DisplayCreation& create, const std::shared_ptr<Event>& event) {
-    ALOGV("Creating display");
-    event->readyToExecute();
-
-    std::lock_guard<std::mutex> lock(mDisplayLock);
-    sp<IBinder> display = SurfaceComposerClient::createDisplay(
-            String8(create.name().c_str()), create.is_secure());
-    mDisplays[create.id()] = display;
-
-    mDisplayCond.notify_all();
-
-    ALOGV("Done creating display");
-}
-
-void Replayer::deleteDisplay(const DisplayDeletion& delete_, const std::shared_ptr<Event>& event) {
-    ALOGV("Delete display");
-    event->readyToExecute();
-
-    std::lock_guard<std::mutex> lock(mDisplayLock);
-    SurfaceComposerClient::destroyDisplay(mDisplays[delete_.id()]);
-    mDisplays.erase(delete_.id());
-}
-
-void Replayer::updatePowerMode(const PowerModeUpdate& pmu, const std::shared_ptr<Event>& event) {
-    ALOGV("Updating power mode");
-    event->readyToExecute();
-    SurfaceComposerClient::setDisplayPowerMode(mDisplays[pmu.id()], pmu.mode());
-}
-
-void Replayer::waitUntilTimestamp(int64_t timestamp) {
-    ALOGV("Waiting for %lld nanoseconds...", static_cast<int64_t>(timestamp - mCurrentTime));
-    std::this_thread::sleep_for(std::chrono::nanoseconds(timestamp - mCurrentTime));
-}
-
-status_t Replayer::loadSurfaceComposerClient() {
-    mComposerClient = new SurfaceComposerClient;
-    return mComposerClient->initCheck();
-}
-
-void Replayer::setReparentChange(SurfaceComposerClient::Transaction& t,
-        layer_id id, const ReparentChange& c) {
-    sp<SurfaceControl> newSurfaceControl = nullptr;
-    if (mLayers.count(c.parent_id()) != 0 && mLayers[c.parent_id()] != nullptr) {
-        newSurfaceControl = mLayers[c.parent_id()];
-    }
-    t.reparent(mLayers[id], newSurfaceControl);
-}
-
-void Replayer::setRelativeParentChange(SurfaceComposerClient::Transaction& t,
-        layer_id id, const RelativeParentChange& c) {
-    if (mLayers.count(c.relative_parent_id()) == 0 || mLayers[c.relative_parent_id()] == nullptr) {
-        ALOGE("Layer %d not found in set relative parent transaction", c.relative_parent_id());
-        return;
-    }
-    t.setRelativeLayer(mLayers[id], mLayers[c.relative_parent_id()], c.z());
-}
-
-void Replayer::setShadowRadiusChange(SurfaceComposerClient::Transaction& t,
-        layer_id id, const ShadowRadiusChange& c) {
-    t.setShadowRadius(mLayers[id], c.radius());
-}
-
-void Replayer::setBlurRegionsChange(SurfaceComposerClient::Transaction& t,
-        layer_id id, const BlurRegionsChange& c) {
-    std::vector<BlurRegion> regions;
-    for(size_t i=0; i < c.blur_regions_size(); i++) {
-        auto protoRegion = c.blur_regions(i);
-        regions.push_back(BlurRegion{
-            .blurRadius = protoRegion.blur_radius(),
-            .alpha = protoRegion.alpha(),
-            .cornerRadiusTL = protoRegion.corner_radius_tl(),
-            .cornerRadiusTR = protoRegion.corner_radius_tr(),
-            .cornerRadiusBL = protoRegion.corner_radius_bl(),
-            .cornerRadiusBR = protoRegion.corner_radius_br(),
-            .left = protoRegion.left(),
-            .top = protoRegion.top(),
-            .right = protoRegion.right(),
-            .bottom = protoRegion.bottom()
-        });
-    }
-    t.setBlurRegions(mLayers[id], regions);
-}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 89c6c0f..0300f8c 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -281,6 +281,18 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.telephony.carrierlock.prebuilt.xml",
+    src: "android.hardware.telephony.carrierlock.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.telephony.data.prebuilt.xml",
+    src: "android.hardware.telephony.data.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.telephony.gsm.prebuilt.xml",
     src: "android.hardware.telephony.gsm.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -293,6 +305,12 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.telephony.ims.singlereg.prebuilt.xml",
+    src: "android.hardware.telephony.ims.singlereg.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.thread_network.prebuilt.xml",
     src: "android.hardware.thread_network.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.location.gps.xml b/data/etc/android.hardware.location.gps.xml
index 72ab732..2a55370 100644
--- a/data/etc/android.hardware.location.gps.xml
+++ b/data/etc/android.hardware.location.gps.xml
@@ -17,6 +17,5 @@
 <!-- These are the location-related features for devices that include GPS. -->
 <permissions>
     <feature name="android.hardware.location" />
-    <feature name="android.hardware.location.network" />
     <feature name="android.hardware.location.gps" />
 </permissions>
diff --git a/data/etc/car_core_hardware.xml b/data/etc/car_core_hardware.xml
index 95b8110..beb69f8 100644
--- a/data/etc/car_core_hardware.xml
+++ b/data/etc/car_core_hardware.xml
@@ -28,7 +28,6 @@
 
     <feature name="android.hardware.audio.output" />
     <feature name="android.hardware.location" />
-    <feature name="android.hardware.location.network" />
     <feature name="android.hardware.bluetooth" />
     <feature name="android.hardware.touchscreen" />
     <feature name="android.hardware.microphone" />
diff --git a/data/etc/input/motion_predictor_config.xml b/data/etc/input/motion_predictor_config.xml
index 39772ae..c3f2fed 100644
--- a/data/etc/input/motion_predictor_config.xml
+++ b/data/etc/input/motion_predictor_config.xml
@@ -31,5 +31,11 @@
        the UX issue mentioned above.
   -->
   <distance-noise-floor>0.2</distance-noise-floor>
+  <!-- The low and high jerk thresholds for prediction pruning.
+
+    The jerk thresholds are based on normalized dt = 1 calculations.
+  -->
+  <low-jerk>1.0</low-jerk>
+  <high-jerk>1.1</high-jerk>
 </motion-predictor>
 
diff --git a/data/etc/wearable_core_hardware.xml b/data/etc/wearable_core_hardware.xml
index 855b110..4c9932d 100644
--- a/data/etc/wearable_core_hardware.xml
+++ b/data/etc/wearable_core_hardware.xml
@@ -36,6 +36,7 @@
     <feature name="android.hardware.security.model.compatible" />
 
     <!-- basic system services -->
+    <feature name="android.software.credentials" />
     <feature name="android.software.home_screen" />
     <feature name="android.software.secure_lock_screen" />
 
diff --git a/include/android/OWNERS b/include/android/OWNERS
index c6cf7ad..fad8c1b 100644
--- a/include/android/OWNERS
+++ b/include/android/OWNERS
@@ -1 +1,5 @@
 per-file input.h,keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS
+
+# Window manager
+per-file surface_control_input_receiver.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file input_transfer_token.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
diff --git a/include/android/asset_manager.h b/include/android/asset_manager.h
index 2ac7d4d..6420cd0 100644
--- a/include/android/asset_manager.h
+++ b/include/android/asset_manager.h
@@ -29,6 +29,10 @@
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
+#if defined(__APPLE__)
+typedef off_t off64_t; // Mac OSX does not define off64_t
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index 6a91d98..bec3283 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -65,9 +65,19 @@
 #ifndef ANDROID_CHOREOGRAPHER_H
 #define ANDROID_CHOREOGRAPHER_H
 
+#include <stddef.h>
 #include <stdint.h>
 #include <sys/cdefs.h>
 
+// This file may also be built on glibc or on Windows/MacOS libc's, so no-op
+// and deprecated definitions are provided.
+#if !defined(__INTRODUCED_IN)
+#define __INTRODUCED_IN(__api_level) /* nothing */
+#endif
+#if !defined(__DEPRECATED_IN)
+#define __DEPRECATED_IN(__api_level, ...) __attribute__((__deprecated__))
+#endif
+
 __BEGIN_DECLS
 
 struct AChoreographer;
diff --git a/include/android/input.h b/include/android/input.h
index 16d86af..fec56f0 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -1490,6 +1490,17 @@
  */
 const AInputEvent* AMotionEvent_fromJava(JNIEnv* env, jobject motionEvent) __INTRODUCED_IN(31);
 
+/**
+ * Creates a java android.view.InputEvent object that is a copy of the specified native
+ * {@link AInputEvent}.
+ *
+ * Specified {@link AInputEvent} is require to be a valid {@link MotionEvent} or {@link KeyEvent}
+ * object.
+ *
+ *  Available since API level 35.
+ */
+jobject AInputEvent_toJava(JNIEnv* env, const AInputEvent* aInputEvent) __INTRODUCED_IN(35);
+
 struct AInputQueue;
 /**
  * Input queue
diff --git a/include/android/input_transfer_token_jni.h b/include/android/input_transfer_token_jni.h
new file mode 100644
index 0000000..92fe9b6
--- /dev/null
+++ b/include/android/input_transfer_token_jni.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 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.
+ */
+/**
+ * @addtogroup NativeActivity Native Activity
+ * @{
+ */
+/**
+ * @file input_transfer_token_jni.h
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <jni.h>
+
+__BEGIN_DECLS
+struct AInputTransferToken;
+
+/**
+ * AInputTransferToken can be used to request focus on or to transfer touch gesture to and from
+ * an embedded SurfaceControl
+ */
+typedef struct AInputTransferToken AInputTransferToken;
+
+/**
+ * Return the AInputTransferToken wrapped by a Java InputTransferToken object. This must be released
+ * using AInputTransferToken_release
+ *
+ * inputTransferTokenObj must be a non-null instance of android.window.InputTransferToken.
+ *
+ * Available since API level 35.
+ */
+AInputTransferToken* _Nonnull AInputTransferToken_fromJava(JNIEnv* _Nonnull env,
+        jobject _Nonnull inputTransferTokenObj) __INTRODUCED_IN(__ANDROID_API_V__);
+/**
+ * Return the Java InputTransferToken object that wraps AInputTransferToken
+ *
+ * aInputTransferToken must be non null and the returned value is an object of instance
+ * android.window.InputTransferToken.
+ *
+ * Available since API level 35.
+ */
+jobject _Nonnull AInputTransferToken_toJava(JNIEnv* _Nonnull env,
+        const AInputTransferToken* _Nonnull aInputTransferToken) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Removes a reference that was previously acquired in native.
+ *
+ * Available since API level 35.
+ */
+void AInputTransferToken_release(AInputTransferToken* _Nullable aInputTransferToken)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
+/** @} */
diff --git a/include/android/keycodes.h b/include/android/keycodes.h
index f8fb256..79cdbca 100644
--- a/include/android/keycodes.h
+++ b/include/android/keycodes.h
@@ -839,6 +839,10 @@
     AKEYCODE_MACRO_3 = 315,
     /** User customizable key #4. */
     AKEYCODE_MACRO_4 = 316,
+    /** Open Emoji picker */
+    AKEYCODE_EMOJI_PICKER = 317,
+    /** Take Screenshot */
+    AKEYCODE_SCREENSHOT = 318,
 
     // NOTE: If you add a new keycode here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 9d2c791..97e4dc0 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -114,6 +114,9 @@
  * API, the client is expected to call {@link APerformanceHint_reportActualWorkDuration} each
  * cycle to report the actual time taken to complete to the system.
  *
+ * Note, methods of {@link APerformanceHintSession_*} are not thread safe so callers must
+ * ensure thread safety.
+ *
  * All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
  */
 typedef struct APerformanceHintSession APerformanceHintSession;
@@ -123,7 +126,8 @@
   *
   * @return APerformanceHintManager instance on success, nullptr on failure.
   */
-APerformanceHintManager* _Nullable APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+APerformanceHintManager* _Nullable APerformanceHint_getManager()
+                         __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Creates a session for the given set of threads and sets their initial target work
@@ -232,14 +236,14 @@
  * @param workDuration The {@link AWorkDuration} structure of times the thread group took to
  *     complete its last task in nanoseconds breaking down into different components.
  *
- *     The work period start timestamp, actual total duration and actual CPU duration must be
- *     positive.
+ *     The work period start timestamp and actual total duration must be greater than zero.
  *
- *     The actual GPU duration must be non-negative. If the actual GPU duration is 0, it means
- *     the actual GPU duration is not measured.
+ *     The actual CPU and GPU durations must be greater than or equal to zero, and at least one
+ *     of them must be greater than zero. When one of them is equal to zero, it means that type
+ *     of work was not measured for this workload.
  *
  * @return 0 on success.
- *         EINVAL if session is nullptr or any duration is an invalid number.
+ *         EINVAL if any duration is an invalid number.
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_reportActualWorkDuration2(
@@ -260,14 +264,15 @@
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
  */
-void AWorkDuration_release(AWorkDuration* _Nonnull WorkDuration) __INTRODUCED_IN(__ANDROID_API_V__);
+void AWorkDuration_release(AWorkDuration* _Nonnull aWorkDuration)
+     __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Sets the work period start timestamp in nanoseconds.
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
  * @param workPeriodStartTimestampNanos The work period start timestamp in nanoseconds based on
- *        CLOCK_MONOTONIC about when the work starts, the timestamp must be positive.
+ *        CLOCK_MONOTONIC about when the work starts. This timestamp must be greater than zero.
  */
 void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* _Nonnull aWorkDuration,
         int64_t workPeriodStartTimestampNanos) __INTRODUCED_IN(__ANDROID_API_V__);
@@ -276,8 +281,8 @@
  * Sets the actual total work duration in nanoseconds.
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
- * @param actualTotalDurationNanos The actual total work duration in nanoseconds, the number must be
- *        positive.
+ * @param actualTotalDurationNanos The actual total work duration in nanoseconds. This number must
+ *        be greater than zero.
  */
 void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
         int64_t actualTotalDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
@@ -286,8 +291,9 @@
  * Sets the actual CPU work duration in nanoseconds.
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
- * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds, the number must be
- *        positive.
+ * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds. This number must be
+ *        greater than or equal to zero. If it is equal to zero, that means the CPU was not
+ *        measured.
  */
 void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
         int64_t actualCpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
@@ -297,7 +303,7 @@
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}.
  * @param actualGpuDurationNanos The actual GPU work duration in nanoseconds, the number must be
- *        non-negative. If the actual GPU duration is 0, it means the actual GPU duration is
+ *        greater than or equal to zero. If it is equal to zero, that means the GPU was not
  *        measured.
  */
 void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 14167e6..099a2bc 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -239,6 +239,10 @@
  * it is acquired. If no acquire_fence_fd was provided, this timestamp will be set to -1.
  *
  * Available since API level 29.
+ *
+ * @deprecated This may return SIGNAL_PENDING because the stats can arrive before the acquire
+ * fence has signaled, depending on internal timing differences. Therefore the caller should
+ * use the acquire fence passed in to setBuffer and query the signal time.
  */
 int64_t ASurfaceTransactionStats_getAcquireTime(
         ASurfaceTransactionStats* _Nonnull surface_transaction_stats,
@@ -537,12 +541,11 @@
 
 /**
  * Sets the desired extended range brightness for the layer. This only applies for layers whose
- * dataspace has RANGE_EXTENDED set on it.
- *
- * Available since API level 34.
+ * dataspace has RANGE_EXTENDED set on it. See: ASurfaceTransaction_setDesiredHdrHeadroom, prefer
+ * using this API for formats that encode an HDR/SDR ratio as part of generating the buffer.
  *
  * @param surface_control The layer whose extended range brightness is being specified
- * @param currentBufferRatio The current hdr/sdr ratio of the current buffer as represented as
+ * @param currentBufferRatio The current HDR/SDR ratio of the current buffer as represented as
  *                           peakHdrBrightnessInNits / targetSdrWhitePointInNits. For example if the
  *                           buffer was rendered with a target SDR whitepoint of 100nits and a max
  *                           display brightness of 200nits, this should be set to 2.0f.
@@ -556,7 +559,7 @@
  *
  *                           Must be finite && >= 1.0f
  *
- * @param desiredRatio The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits /
+ * @param desiredRatio The desired HDR/SDR ratio as represented as peakHdrBrightnessInNits /
  *                     targetSdrWhitePointInNits. This can be used to communicate the max desired
  *                     brightness range. This is similar to the "max luminance" value in other
  *                     HDR metadata formats, but represented as a ratio of the target SDR whitepoint
@@ -573,6 +576,12 @@
  *                     determined entirely by the dataspace being used (ie, typically SDR
  *                     however PQ or HLG transfer functions will still result in HDR)
  *
+ *                     When called after ASurfaceTransaction_setDesiredHdrHeadroom, the
+ *                     desiredRatio will override the desiredHeadroom provided by
+ *                     ASurfaceTransaction_setDesiredHdrHeadroom. Conversely, when called before
+ *                     ASurfaceTransaction_setDesiredHdrHeadroom, the desiredHeadroom provided by
+ *.                    ASurfaceTransaction_setDesiredHdrHeadroom will override the desiredRatio.
+ *
  *                     Must be finite && >= 1.0f
  *
  * Available since API level 34.
@@ -583,6 +592,45 @@
                                                     __INTRODUCED_IN(__ANDROID_API_U__);
 
 /**
+ * Sets the desired HDR headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness,
+ * prefer using this API for formats that conform to HDR standards like HLG or HDR10, that do not
+ * communicate a HDR/SDR ratio as part of generating the buffer.
+ *
+ * @param surface_control The layer whose desired HDR headroom is being specified
+ *
+ * @param desiredHeadroom The desired HDR/SDR ratio as represented as peakHdrBrightnessInNits /
+ *                        targetSdrWhitePointInNits. This can be used to communicate the max
+ *                        desired brightness range of the panel. The system may not be able to, or
+ *                        may choose not to, deliver the requested range.
+ *
+ *                        While requesting a large desired ratio will result in the most
+ *                        dynamic range, voluntarily reducing the requested range can help
+ *                        improve battery life as well as can improve quality by ensuring
+ *                        greater bit depth is allocated to the luminance range in use.
+ *
+ *                        Default value is 0.0f and indicates that the system will choose the best
+ *                        headroom for this surface control's content. Typically, this means that
+ *                        HLG/PQ encoded content will be displayed with some HDR headroom greater
+ *                        than 1.0.
+ *
+ *                        When called after ASurfaceTransaction_setExtendedRangeBrightness, the
+ *                        desiredHeadroom will override the desiredRatio provided by
+ *                        ASurfaceTransaction_setExtendedRangeBrightness. Conversely, when called
+ *                        before ASurfaceTransaction_setExtendedRangeBrightness, the desiredRatio
+ *                        provided by ASurfaceTransaction_setExtendedRangeBrightness will override
+ *                        the desiredHeadroom.
+ *
+ *                        Must be finite && >= 1.0f or 0.0f to indicate there is no desired
+ *                        headroom.
+ *
+ * Available since API level 35.
+ */
+void ASurfaceTransaction_setDesiredHdrHeadroom(ASurfaceTransaction* _Nonnull transaction,
+                                               ASurfaceControl* _Nonnull surface_control,
+                                               float desiredHeadroom)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
  * Same as ASurfaceTransaction_setFrameRateWithChangeStrategy(transaction, surface_control,
  * frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS).
  *
diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h
new file mode 100644
index 0000000..bdc5249
--- /dev/null
+++ b/include/android/surface_control_input_receiver.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+/**
+ * @addtogroup NativeActivity Native Activity
+ * @{
+ */
+/**
+ * @file surface_control_input_receiver.h
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <android/input.h>
+#include <android/surface_control.h>
+#include <android/input_transfer_token_jni.h>
+
+__BEGIN_DECLS
+
+/**
+ * The AInputReceiver_onMotionEvent callback is invoked when the registered input channel receives
+ * a motion event.
+ *
+ * \param context Optional context provided by the client that is passed when creating the
+ * AInputReceiverCallbacks.
+ *
+ * \param motionEvent The motion event. This must be released with AInputEvent_release.
+ *
+ * Available since API level 35.
+ */
+typedef bool (*AInputReceiver_onMotionEvent)(void *_Null_unspecified context,
+                                             AInputEvent *_Nonnull motionEvent)
+                                            __INTRODUCED_IN(__ANDROID_API_V__);
+/**
+ * The AInputReceiver_onKeyEvent callback is invoked when the registered input channel receives
+ * a key event.
+ *
+ * \param context Optional context provided by the client that is passed when creating the
+ * AInputReceiverCallbacks.
+ *
+ * \param keyEvent The key event. This must be released with AInputEvent_release.
+ *
+ * Available since API level 35.
+ */
+typedef bool (*AInputReceiver_onKeyEvent)(void *_Null_unspecified context,
+                                          AInputEvent *_Nonnull keyEvent)
+                                          __INTRODUCED_IN(__ANDROID_API_V__);
+
+struct AInputReceiverCallbacks;
+
+struct AInputReceiver;
+
+/**
+ * The InputReceiver that holds the reference to the registered input channel. This must be released
+ * using AInputReceiver_release
+ *
+ * Available since API level 35.
+ */
+typedef struct AInputReceiver AInputReceiver __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Registers an input receiver for an ASurfaceControl that will receive batched input event. For
+ * those events that are batched, the invocation will happen once per AChoreographer frame, and
+ * other input events will be delivered immediately.
+ *
+ * This is different from AInputReceiver_createUnbatchedInputReceiver in that the input events are
+ * received batched. The caller must invoke AInputReceiver_release to clean up the resources when
+ * no longer needing to use the input receiver.
+ *
+ * \param aChoreographer         The AChoreographer used for batching. This should match the
+ *                               rendering AChoreographer.
+ * \param hostInputTransferToken The host token to link the embedded. This is used to handle
+ *                               transferring touch gesture from host to embedded and for ANRs
+ *                               to ensure the host receives the ANR if any issues with
+ *                               touch on the embedded. This can be retrieved for the host window
+ *                               by calling AttachedSurfaceControl#getInputTransferToken()
+ * \param aSurfaceControl        The ASurfaceControl to register the InputChannel for
+ * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events
+ *
+ * Returns the reference to AInputReceiver to clean up resources when done.
+ *
+ * Available since API level 35.
+ */
+AInputReceiver* _Nonnull
+AInputReceiver_createBatchedInputReceiver(AChoreographer* _Nonnull aChoreographer,
+                                        const AInputTransferToken* _Nonnull hostInputTransferToken,
+                                        const ASurfaceControl* _Nonnull aSurfaceControl,
+                                        AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks)
+                                        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Registers an input receiver for an ASurfaceControl that will receive every input event.
+ * This is different from AInputReceiver_createBatchedInputReceiver in that the input events are
+ * received unbatched. The caller must invoke AInputReceiver_release to clean up the resources when
+ * no longer needing to use the input receiver.
+ *
+ * \param aLooper                The looper to use when invoking callbacks.
+ * \param hostInputTransferToken The host token to link the embedded. This is used to handle
+ *                               transferring touch gesture from host to embedded and for ANRs
+ *                               to ensure the host receives the ANR if any issues with
+ *                               touch on the embedded. This can be retrieved for the host window
+ *                               by calling AttachedSurfaceControl#getInputTransferToken()
+ * \param aSurfaceControl        The ASurfaceControl to register the InputChannel for
+ * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events
+ *
+ * Returns the reference to AInputReceiver to clean up resources when done.
+ *
+ * Available since API level 35.
+ */
+AInputReceiver* _Nonnull
+AInputReceiver_createUnbatchedInputReceiver(ALooper* _Nonnull aLooper,
+                                         const AInputTransferToken* _Nonnull hostInputTransferToken,
+                                         const ASurfaceControl* _Nonnull aSurfaceControl,
+                                         AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks)
+                                         __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Returns the AInputTransferToken that can be used to transfer touch gesture to or from other
+ * windows. This InputTransferToken is associated with the SurfaceControl that registered an input
+ * receiver and can be used with the host token for things like transfer touch gesture via
+ * WindowManager#transferTouchGesture().
+ *
+ * This must be released with AInputTransferToken_release.
+ *
+ * \param aInputReceiver The inputReceiver object to retrieve the AInputTransferToken for.
+ *
+ * Available since API level 35.
+ */
+const AInputTransferToken *_Nonnull
+AInputReceiver_getInputTransferToken(AInputReceiver *_Nonnull aInputReceiver)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Unregisters the input channel and deletes the AInputReceiver. This must be called on the same
+ * looper thread it was created with.
+ *
+ * \param aInputReceiver The inputReceiver object to release.
+ *
+ * Available since API level 35.
+ */
+void
+AInputReceiver_release(AInputReceiver *_Nullable aInputReceiver) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Creates a AInputReceiverCallbacks object that is used when registering for an AInputReceiver.
+ * This must be released using AInputReceiverCallbacks_release
+ *
+ * \param context Optional context provided by the client that will be passed into the callbacks.
+ *
+ * Available since API level 35.
+ */
+AInputReceiverCallbacks* _Nonnull AInputReceiverCallbacks_create(void* _Nullable context)
+                                                        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Releases the AInputReceiverCallbacks. This must be called on the same
+ * looper thread the AInputReceiver was created with. The receiver will not invoke any callbacks
+ * once it's been released.
+ *
+ * Available since API level 35
+ */
+void AInputReceiverCallbacks_release(AInputReceiverCallbacks* _Nullable callbacks)
+                                     __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets a AInputReceiver_onMotionEvent callback for an AInputReceiverCallbacks
+ *
+ * \param callbacks The callback object to set the motion event on.
+ * \param onMotionEvent The motion event that will be invoked
+ *
+ * Available since API level 35.
+ */
+void AInputReceiverCallbacks_setMotionEventCallback(AInputReceiverCallbacks* _Nonnull callbacks,
+                                                AInputReceiver_onMotionEvent _Nonnull onMotionEvent)
+                                                __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets a AInputReceiver_onKeyEvent callback for an AInputReceiverCallbacks
+ *
+ * \param callbacks The callback object to set the motion event on.
+ * \param onMotionEvent The key event that will be invoked
+ *
+ * Available since API level 35.
+ */
+void AInputReceiverCallbacks_setKeyEventCallback(AInputReceiverCallbacks* _Nonnull callbacks,
+                                                 AInputReceiver_onKeyEvent _Nonnull onKeyEvent)
+                                                 __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 0b57e93..fa168cd 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -111,7 +111,7 @@
  * It's passed the updated thermal status as parameter, as well as the
  * pointer provided by the client that registered a callback.
  */
-typedef void (*AThermal_StatusCallback)(void* data, AThermalStatus status);
+typedef void (*AThermal_StatusCallback)(void* _Nullable data, AThermalStatus status);
 
 /**
   * Acquire an instance of the thermal manager. This must be freed using
@@ -121,7 +121,7 @@
   *
   * @return manager instance on success, nullptr on failure.
   */
-AThermalManager* AThermal_acquireManager() __INTRODUCED_IN(30);
+AThermalManager* _Nonnull AThermal_acquireManager() __INTRODUCED_IN(30);
 
 /**
  * Release the thermal manager pointer acquired via
@@ -131,7 +131,7 @@
  *
  * @param manager The manager to be released.
  */
-void AThermal_releaseManager(AThermalManager *manager) __INTRODUCED_IN(30);
+void AThermal_releaseManager(AThermalManager* _Nonnull manager) __INTRODUCED_IN(30);
 
 /**
   * Gets the current thermal status.
@@ -143,7 +143,8 @@
   *
   * @return current thermal status, ATHERMAL_STATUS_ERROR on failure.
   */
-AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) __INTRODUCED_IN(30);
+AThermalStatus
+AThermal_getCurrentThermalStatus(AThermalManager* _Nonnull manager) __INTRODUCED_IN(30);
 
 /**
  * Register the thermal status listener for thermal status change.
@@ -160,8 +161,9 @@
  *         EPERM if the required permission is not held.
  *         EPIPE if communication with the system service has failed.
  */
-int AThermal_registerThermalStatusListener(AThermalManager *manager,
-        AThermal_StatusCallback callback, void *data) __INTRODUCED_IN(30);
+int AThermal_registerThermalStatusListener(AThermalManager* _Nonnull manager,
+                                           AThermal_StatusCallback _Nullable callback,
+                                           void* _Nullable data) __INTRODUCED_IN(30);
 
 /**
  * Unregister the thermal status listener previously resgistered.
@@ -178,8 +180,9 @@
  *         EPERM if the required permission is not held.
  *         EPIPE if communication with the system service has failed.
  */
-int AThermal_unregisterThermalStatusListener(AThermalManager *manager,
-        AThermal_StatusCallback callback, void *data) __INTRODUCED_IN(30);
+int AThermal_unregisterThermalStatusListener(AThermalManager* _Nonnull manager,
+                                             AThermal_StatusCallback _Nullable callback,
+                                             void* _Nullable data) __INTRODUCED_IN(30);
 
 /**
  * Provides an estimate of how much thermal headroom the device currently has before
@@ -219,8 +222,8 @@
  *         as described above. Returns NaN if the device does not support this functionality or
  *         if this function is called significantly faster than once per second.
   */
-float AThermal_getThermalHeadroom(AThermalManager *manager,
-        int forecastSeconds) __INTRODUCED_IN(31);
+float AThermal_getThermalHeadroom(AThermalManager* _Nonnull manager,
+                                  int forecastSeconds) __INTRODUCED_IN(31);
 
 /**
  * This struct defines an instance of headroom threshold value and its status.
@@ -282,9 +285,10 @@
  *         EPIPE if communication with the system service has failed.
  *         ENOSYS if the feature is disabled by the current system.
  */
-int AThermal_getThermalHeadroomThresholds(AThermalManager* manager,
-                                          const AThermalHeadroomThreshold ** outThresholds,
-                                          size_t* size) __INTRODUCED_IN(35);
+int AThermal_getThermalHeadroomThresholds(AThermalManager* _Nonnull manager,
+                                          const AThermalHeadroomThreshold* _Nonnull
+                                          * _Nullable outThresholds,
+                                          size_t* _Nonnull size) __INTRODUCED_IN(35);
 
 #ifdef __cplusplus
 }
diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h
index c0f6768..68826bb 100644
--- a/include/ftl/algorithm.h
+++ b/include/ftl/algorithm.h
@@ -24,6 +24,18 @@
 
 namespace android::ftl {
 
+// Determines if a container contains a value. This is a simplified version of the C++23
+// std::ranges::contains function.
+//
+//   const ftl::StaticVector vector = {1, 2, 3};
+//   assert(ftl::contains(vector, 1));
+//
+// TODO: Remove in C++23.
+template <typename Container, typename Value>
+auto contains(const Container& container, const Value& value) -> bool {
+  return std::find(container.begin(), container.end(), value) != container.end();
+}
+
 // Adapter for std::find_if that converts the return value from iterator to optional.
 //
 //   const ftl::StaticVector vector = {"upside"sv, "down"sv, "cake"sv};
diff --git a/include/ftl/concat.h b/include/ftl/concat.h
index e0774d3..7680bed 100644
--- a/include/ftl/concat.h
+++ b/include/ftl/concat.h
@@ -57,7 +57,7 @@
 template <std::size_t N>
 struct Concat<N> {
   static constexpr std::size_t max_size() { return N; }
-  constexpr std::size_t size() const { return end_ - buffer_; }
+  constexpr std::size_t size() const { return static_cast<std::size_t>(end_ - buffer_); }
 
   constexpr const char* c_str() const { return buffer_; }
 
@@ -68,6 +68,8 @@
 
  protected:
   constexpr Concat() : end_(buffer_) {}
+  constexpr Concat(const Concat&) = delete;
+
   constexpr void append() { *end_ = '\0'; }
 
   char buffer_[N + 1];
diff --git a/include/ftl/details/future.h b/include/ftl/details/future.h
index df1323e..8d82e0f 100644
--- a/include/ftl/details/future.h
+++ b/include/ftl/details/future.h
@@ -73,8 +73,18 @@
     return std::get<Impl>(self()).get();
   }
 
+  template <class Rep, class Period>
+  std::future_status wait_for(const std::chrono::duration<Rep, Period>& timeout_duration) const {
+    if (std::holds_alternative<T>(self())) {
+      return std::future_status::ready;
+    }
+
+    return std::get<Impl>(self()).wait_for(timeout_duration);
+  }
+
  private:
   auto& self() { return static_cast<Self&>(*this).future_; }
+  const auto& self() const { return static_cast<const Self&>(*this).future_; }
 };
 
 template <typename Self, typename T>
@@ -90,6 +100,15 @@
     return std::get<Impl>(self()).get();
   }
 
+  template <class Rep, class Period>
+  std::future_status wait_for(const std::chrono::duration<Rep, Period>& timeout_duration) const {
+    if (std::holds_alternative<T>(self())) {
+      return std::future_status::ready;
+    }
+
+    return std::get<Impl>(self()).wait_for(timeout_duration);
+  }
+
  private:
   const auto& self() const { return static_cast<const Self&>(*this).future_; }
 };
diff --git a/include/ftl/details/hash.h b/include/ftl/details/hash.h
new file mode 100644
index 0000000..230ae51
--- /dev/null
+++ b/include/ftl/details/hash.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2024 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 <cinttypes>
+#include <cstring>
+
+namespace android::ftl::details {
+
+// Based on CityHash64 v1.0.1 (http://code.google.com/p/cityhash/), but slightly
+// modernized and trimmed for cases with bounded lengths.
+
+template <typename T = std::uint64_t>
+inline T read_unaligned(const void* ptr) {
+  T v;
+  std::memcpy(&v, ptr, sizeof(T));
+  return v;
+}
+
+template <bool NonZeroShift = false>
+constexpr std::uint64_t rotate(std::uint64_t v, std::uint8_t shift) {
+  if constexpr (!NonZeroShift) {
+    if (shift == 0) return v;
+  }
+  return (v >> shift) | (v << (64 - shift));
+}
+
+constexpr std::uint64_t shift_mix(std::uint64_t v) {
+  return v ^ (v >> 47);
+}
+
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+constexpr std::uint64_t hash_length_16(std::uint64_t u, std::uint64_t v) {
+  constexpr std::uint64_t kPrime = 0x9ddfea08eb382d69ull;
+  auto a = (u ^ v) * kPrime;
+  a ^= (a >> 47);
+  auto b = (v ^ a) * kPrime;
+  b ^= (b >> 47);
+  b *= kPrime;
+  return b;
+}
+
+constexpr std::uint64_t kPrime0 = 0xc3a5c85c97cb3127ull;
+constexpr std::uint64_t kPrime1 = 0xb492b66fbe98f273ull;
+constexpr std::uint64_t kPrime2 = 0x9ae16a3b2f90404full;
+constexpr std::uint64_t kPrime3 = 0xc949d7c7509e6557ull;
+
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+inline std::uint64_t hash_length_0_to_16(const char* str, std::uint64_t length) {
+  if (length > 8) {
+    const auto a = read_unaligned(str);
+    const auto b = read_unaligned(str + length - 8);
+    return hash_length_16(a, rotate<true>(b + length, static_cast<std::uint8_t>(length))) ^ b;
+  }
+  if (length >= 4) {
+    const auto a = read_unaligned<std::uint32_t>(str);
+    const auto b = read_unaligned<std::uint32_t>(str + length - 4);
+    return hash_length_16(length + (a << 3), b);
+  }
+  if (length > 0) {
+    const auto a = static_cast<unsigned char>(str[0]);
+    const auto b = static_cast<unsigned char>(str[length >> 1]);
+    const auto c = static_cast<unsigned char>(str[length - 1]);
+    const auto y = static_cast<std::uint32_t>(a) + (static_cast<std::uint32_t>(b) << 8);
+    const auto z = static_cast<std::uint32_t>(length) + (static_cast<std::uint32_t>(c) << 2);
+    return shift_mix(y * kPrime2 ^ z * kPrime3) * kPrime2;
+  }
+  return kPrime2;
+}
+
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+inline std::uint64_t hash_length_17_to_32(const char* str, std::uint64_t length) {
+  const auto a = read_unaligned(str) * kPrime1;
+  const auto b = read_unaligned(str + 8);
+  const auto c = read_unaligned(str + length - 8) * kPrime2;
+  const auto d = read_unaligned(str + length - 16) * kPrime0;
+  return hash_length_16(rotate(a - b, 43) + rotate(c, 30) + d,
+                        a + rotate(b ^ kPrime3, 20) - c + length);
+}
+
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+inline std::uint64_t hash_length_33_to_64(const char* str, std::uint64_t length) {
+  auto z = read_unaligned(str + 24);
+  auto a = read_unaligned(str) + (length + read_unaligned(str + length - 16)) * kPrime0;
+  auto b = rotate(a + z, 52);
+  auto c = rotate(a, 37);
+
+  a += read_unaligned(str + 8);
+  c += rotate(a, 7);
+  a += read_unaligned(str + 16);
+
+  const auto vf = a + z;
+  const auto vs = b + rotate(a, 31) + c;
+
+  a = read_unaligned(str + 16) + read_unaligned(str + length - 32);
+  z += read_unaligned(str + length - 8);
+  b = rotate(a + z, 52);
+  c = rotate(a, 37);
+  a += read_unaligned(str + length - 24);
+  c += rotate(a, 7);
+  a += read_unaligned(str + length - 16);
+
+  const auto wf = a + z;
+  const auto ws = b + rotate(a, 31) + c;
+  const auto r = shift_mix((vf + ws) * kPrime2 + (wf + vs) * kPrime0);
+  return shift_mix(r * kPrime0 + vs) * kPrime2;
+}
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/expected.h b/include/ftl/expected.h
new file mode 100644
index 0000000..57448dc
--- /dev/null
+++ b/include/ftl/expected.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/expected.h>
+#include <ftl/optional.h>
+#include <ftl/unit.h>
+
+#include <utility>
+
+// Given an expression `expr` that evaluates to an ftl::Expected<T, E> result (R for short), FTL_TRY
+// unwraps T out of R, or bails out of the enclosing function F if R has an error E. The return type
+// of F must be R, since FTL_TRY propagates R in the error case. As a special case, ftl::Unit may be
+// used as the error E to allow FTL_TRY expressions when F returns `void`.
+//
+// The non-standard syntax requires `-Wno-gnu-statement-expression-from-macro-expansion` to compile.
+// The UnitToVoid conversion allows the macro to be used for early exit from a function that returns
+// `void`.
+//
+// Example usage:
+//
+//   using StringExp = ftl::Expected<std::string, std::errc>;
+//
+//   StringExp repeat(StringExp exp) {
+//     const std::string str = FTL_TRY(exp);
+//     return StringExp(str + str);
+//   }
+//
+//   assert(StringExp("haha"s) == repeat(StringExp("ha"s)));
+//   assert(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
+//     return e == std::errc::bad_message;
+//   }));
+//
+//
+// FTL_TRY may be used in void-returning functions by using ftl::Unit as the error type:
+//
+//   void uppercase(char& c, ftl::Optional<char> opt) {
+//     c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit())));
+//   }
+//
+//   char c = '?';
+//   uppercase(c, std::nullopt);
+//   assert(c == '?');
+//
+//   uppercase(c, 'a');
+//   assert(c == 'A');
+//
+#define FTL_TRY(expr)                                                     \
+  ({                                                                      \
+    auto exp_ = (expr);                                                   \
+    if (!exp_.has_value()) {                                              \
+      using E = decltype(exp_)::error_type;                               \
+      return android::ftl::details::UnitToVoid<E>::from(std::move(exp_)); \
+    }                                                                     \
+    exp_.value();                                                         \
+  })
+
+namespace android::ftl {
+
+// Superset of base::expected<T, E> with monadic operations.
+//
+// TODO: Extend std::expected<T, E> in C++23.
+//
+template <typename T, typename E>
+struct Expected final : base::expected<T, E> {
+  using Base = base::expected<T, E>;
+  using Base::expected;
+
+  using Base::error;
+  using Base::has_value;
+  using Base::value;
+
+  template <typename P>
+  constexpr bool has_error(P predicate) const {
+    return !has_value() && predicate(error());
+  }
+
+  constexpr Optional<T> value_opt() const& {
+    return has_value() ? Optional(value()) : std::nullopt;
+  }
+
+  constexpr Optional<T> value_opt() && {
+    return has_value() ? Optional(std::move(value())) : std::nullopt;
+  }
+
+  // Delete new for this class. Its base doesn't have a virtual destructor, and
+  // if it got deleted via base class pointer, it would cause undefined
+  // behavior. There's not a good reason to allocate this object on the heap
+  // anyway.
+  static void* operator new(size_t) = delete;
+  static void* operator new[](size_t) = delete;
+};
+
+template <typename E>
+constexpr auto Unexpected(E&& error) {
+  return base::unexpected(std::forward<E>(error));
+}
+
+}  // namespace android::ftl
diff --git a/include/ftl/fake_guard.h b/include/ftl/fake_guard.h
index bacd1b2..e601251 100644
--- a/include/ftl/fake_guard.h
+++ b/include/ftl/fake_guard.h
@@ -85,6 +85,5 @@
 
 #define FTL_MAKE_FAKE_GUARD(arg1, arg2, guard, ...) guard
 
-// The void argument suppresses a warning about zero variadic macro arguments.
 #define FTL_FAKE_GUARD(...) \
-  FTL_MAKE_FAKE_GUARD(__VA_ARGS__, FTL_FAKE_GUARD2, FTL_FAKE_GUARD1, void)(__VA_ARGS__)
+  FTL_MAKE_FAKE_GUARD(__VA_ARGS__, FTL_FAKE_GUARD2, FTL_FAKE_GUARD1, )(__VA_ARGS__)
diff --git a/include/ftl/future.h b/include/ftl/future.h
index c78f9b7..dad180f 100644
--- a/include/ftl/future.h
+++ b/include/ftl/future.h
@@ -51,6 +51,7 @@
   // Forwarding functions. Base::share is only defined when FutureImpl is std::future, whereas the
   // following are defined for either FutureImpl:
   using Base::get;
+  using Base::wait_for;
 
   // Attaches a continuation to the future. The continuation is a function that maps T to either R
   // or ftl::Future<R>. In the former case, the chain wraps the result in a future as if by
diff --git a/include/ftl/hash.h b/include/ftl/hash.h
new file mode 100644
index 0000000..29a6f71
--- /dev/null
+++ b/include/ftl/hash.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 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 <cinttypes>
+#include <optional>
+#include <string_view>
+
+#include <ftl/details/hash.h>
+
+namespace android::ftl {
+
+// Non-cryptographic hash function (namely CityHash64) for strings with at most 64 characters.
+// Unlike std::hash, which returns std::size_t and is only required to produce the same result
+// for the same input within a single execution of a program, this hash is stable.
+inline std::optional<std::uint64_t> stable_hash(std::string_view view) {
+  const auto length = view.length();
+  if (length <= 16) {
+    return details::hash_length_0_to_16(view.data(), length);
+  }
+  if (length <= 32) {
+    return details::hash_length_17_to_32(view.data(), length);
+  }
+  if (length <= 64) {
+    return details::hash_length_33_to_64(view.data(), length);
+  }
+  return {};
+}
+
+}  // namespace android::ftl
diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h
index 35d09d7..4a5d8bf 100644
--- a/include/ftl/non_null.h
+++ b/include/ftl/non_null.h
@@ -68,26 +68,28 @@
   constexpr NonNull(const NonNull&) = default;
   constexpr NonNull& operator=(const NonNull&) = default;
 
-  constexpr const Pointer& get() const { return pointer_; }
-  constexpr explicit operator const Pointer&() const { return get(); }
+  [[nodiscard]] constexpr const Pointer& get() const { return pointer_; }
+  [[nodiscard]] constexpr explicit operator const Pointer&() const { return get(); }
 
   // Move operations. These break the invariant, so care must be taken to avoid subsequent access.
 
   constexpr NonNull(NonNull&&) = default;
   constexpr NonNull& operator=(NonNull&&) = default;
 
-  constexpr Pointer take() && { return std::move(pointer_); }
-  constexpr explicit operator Pointer() && { return take(); }
+  [[nodiscard]] constexpr Pointer take() && { return std::move(pointer_); }
+  [[nodiscard]] constexpr explicit operator Pointer() && { return take(); }
 
   // Dereferencing.
-  constexpr decltype(auto) operator*() const { return *get(); }
-  constexpr decltype(auto) operator->() const { return get(); }
+  [[nodiscard]] constexpr decltype(auto) operator*() const { return *get(); }
+  [[nodiscard]] constexpr decltype(auto) operator->() const { return get(); }
+
+  [[nodiscard]] constexpr explicit operator bool() const { return !(pointer_ == nullptr); }
 
   // Private constructor for ftl::as_non_null. Excluded from candidate constructors for conversions
   // through the passkey idiom, for clear compilation errors.
   template <typename P>
   constexpr NonNull(Passkey, P&& pointer) : pointer_(std::forward<P>(pointer)) {
-    if (!pointer_) std::abort();
+    if (pointer_ == nullptr) std::abort();
   }
 
  private:
@@ -98,11 +100,13 @@
 };
 
 template <typename P>
-constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> {
+[[nodiscard]] constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> {
   using Passkey = typename NonNull<std::decay_t<P>>::Passkey;
   return {Passkey{}, std::forward<P>(pointer)};
 }
 
+// NonNull<P> <=> NonNull<Q>
+
 template <typename P, typename Q>
 constexpr bool operator==(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
   return lhs.get() == rhs.get();
@@ -113,4 +117,96 @@
   return !operator==(lhs, rhs);
 }
 
+template <typename P, typename Q>
+constexpr bool operator<(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() < rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() <= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() >= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() > rhs.get();
+}
+
+// NonNull<P> <=> Q
+
+template <typename P, typename Q>
+constexpr bool operator==(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() == rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator!=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() != rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator<(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() < rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() <= rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() >= rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() > rhs;
+}
+
+// P <=> NonNull<Q>
+
+template <typename P, typename Q>
+constexpr bool operator==(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs == rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator!=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs != rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs < rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs <= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs >= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs > rhs.get();
+}
+
 }  // namespace android::ftl
+
+// Specialize std::hash for ftl::NonNull<T>
+template <typename P>
+struct std::hash<android::ftl::NonNull<P>> {
+  std::size_t operator()(const android::ftl::NonNull<P>& ptr) const {
+    return std::hash<P>()(ptr.get());
+  }
+};
diff --git a/include/ftl/optional.h b/include/ftl/optional.h
index 94d8e3d..e245d88 100644
--- a/include/ftl/optional.h
+++ b/include/ftl/optional.h
@@ -20,13 +20,14 @@
 #include <optional>
 #include <utility>
 
+#include <android-base/expected.h>
 #include <ftl/details/optional.h>
 
 namespace android::ftl {
 
 // Superset of std::optional<T> with monadic operations, as proposed in https://wg21.link/P0798R8.
 //
-// TODO: Remove in C++23.
+// TODO: Remove standard APIs in C++23.
 //
 template <typename T>
 struct Optional final : std::optional<T> {
@@ -109,6 +110,13 @@
     return std::forward<F>(f)();
   }
 
+  // Maps this Optional<T> to expected<T, E> where nullopt becomes E.
+  template <typename E>
+  constexpr auto ok_or(E&& e) && -> base::expected<T, E> {
+    if (has_value()) return std::move(value());
+    return base::unexpected(std::forward<E>(e));
+  }
+
   // Delete new for this class. Its base doesn't have a virtual destructor, and
   // if it got deleted via base class pointer, it would cause undefined
   // behavior. There's not a good reason to allocate this object on the heap
diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h
index 49cde7f..83d5967 100644
--- a/include/ftl/small_map.h
+++ b/include/ftl/small_map.h
@@ -107,12 +107,20 @@
   template <typename Q, typename W, std::size_t M, typename E>
   SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {}
 
+  static constexpr size_type static_capacity() { return N; }
+
   size_type max_size() const { return map_.max_size(); }
   size_type size() const { return map_.size(); }
   bool empty() const { return map_.empty(); }
 
   // Returns whether the map is backed by static or dynamic storage.
-  bool dynamic() const { return map_.dynamic(); }
+  bool dynamic() const {
+    if constexpr (static_capacity() > 0) {
+      return map_.dynamic();
+    } else {
+      return true;
+    }
+  }
 
   iterator begin() { return map_.begin(); }
   const_iterator begin() const { return cbegin(); }
@@ -171,9 +179,15 @@
       return {it, false};
     }
 
-    auto& ref = map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key),
-                                  std::forward_as_tuple(std::forward<Args>(args)...));
-    return {&ref, true};
+    decltype(auto) ref_or_it =
+        map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key),
+                          std::forward_as_tuple(std::forward<Args>(args)...));
+
+    if constexpr (static_capacity() > 0) {
+      return {&ref_or_it, true};
+    } else {
+      return {ref_or_it, true};
+    }
   }
 
   // Replaces a mapping if it exists, and returns an iterator to it. Returns the end() iterator
diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h
index 11294c3..43e9fac 100644
--- a/include/ftl/small_vector.h
+++ b/include/ftl/small_vector.h
@@ -124,30 +124,29 @@
   DISPATCH(size_type, size, const)
   DISPATCH(bool, empty, const)
 
-  // noexcept to suppress warning about zero variadic macro arguments.
-  DISPATCH(iterator, begin, noexcept)
+  DISPATCH(iterator, begin, )
   DISPATCH(const_iterator, begin, const)
   DISPATCH(const_iterator, cbegin, const)
 
-  DISPATCH(iterator, end, noexcept)
+  DISPATCH(iterator, end, )
   DISPATCH(const_iterator, end, const)
   DISPATCH(const_iterator, cend, const)
 
-  DISPATCH(reverse_iterator, rbegin, noexcept)
+  DISPATCH(reverse_iterator, rbegin, )
   DISPATCH(const_reverse_iterator, rbegin, const)
   DISPATCH(const_reverse_iterator, crbegin, const)
 
-  DISPATCH(reverse_iterator, rend, noexcept)
+  DISPATCH(reverse_iterator, rend, )
   DISPATCH(const_reverse_iterator, rend, const)
   DISPATCH(const_reverse_iterator, crend, const)
 
-  DISPATCH(iterator, last, noexcept)
+  DISPATCH(iterator, last, )
   DISPATCH(const_iterator, last, const)
 
-  DISPATCH(reference, front, noexcept)
+  DISPATCH(reference, front, )
   DISPATCH(const_reference, front, const)
 
-  DISPATCH(reference, back, noexcept)
+  DISPATCH(reference, back, )
   DISPATCH(const_reference, back, const)
 
   reference operator[](size_type i) {
@@ -211,13 +210,13 @@
   //
   // The last() and end() iterators are invalidated.
   //
-  DISPATCH(void, pop_back, noexcept)
+  DISPATCH(void, pop_back, )
 
   // Removes all elements.
   //
   // All iterators are invalidated.
   //
-  DISPATCH(void, clear, noexcept)
+  DISPATCH(void, clear, )
 
 #undef DISPATCH
 
diff --git a/include/ftl/unit.h b/include/ftl/unit.h
index e38230b..62549a3 100644
--- a/include/ftl/unit.h
+++ b/include/ftl/unit.h
@@ -58,4 +58,22 @@
   return {std::forward<F>(f)};
 }
 
+namespace details {
+
+// Identity function for all T except Unit, which maps to void.
+template <typename T>
+struct UnitToVoid {
+  template <typename U>
+  static auto from(U&& value) {
+    return value;
+  }
+};
+
+template <>
+struct UnitToVoid<Unit> {
+  template <typename U>
+  static void from(U&&) {}
+};
+
+}  // namespace details
 }  // namespace android::ftl
diff --git a/include/input/AccelerationCurve.h b/include/input/AccelerationCurve.h
new file mode 100644
index 0000000..0cf648a
--- /dev/null
+++ b/include/input/AccelerationCurve.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 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 <vector>
+
+namespace android {
+
+/**
+ * Describes a section of an acceleration curve as a function which outputs a scaling factor (gain)
+ * for the pointer movement, given the speed of the mouse or finger (in mm/s):
+ *
+ *     gain(input_speed_mm_per_s) = baseGain + reciprocal / input_speed_mm_per_s
+ */
+struct AccelerationCurveSegment {
+    /**
+     * The maximum pointer speed at which this segment should apply, in mm/s. The last segment in a
+     * curve should always set this to infinity.
+     */
+    double maxPointerSpeedMmPerS;
+    /** The gain for this segment before the reciprocal is taken into account. */
+    double baseGain;
+    /** The reciprocal part of the formula, which should be divided by the input speed. */
+    double reciprocal;
+};
+
+/**
+ * Creates an acceleration curve for the given pointer sensitivity value. The sensitivity value
+ * should be between -7 (for the lowest sensitivity) and 7, inclusive.
+ */
+std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
+        int32_t sensitivity);
+
+} // namespace android
diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h
index b0eceef..56294dd 100644
--- a/include/input/DisplayViewport.h
+++ b/include/input/DisplayViewport.h
@@ -19,7 +19,6 @@
 #include <android-base/stringprintf.h>
 #include <ftl/enum.h>
 #include <ftl/string.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <ui/Rotation.h>
 
@@ -47,7 +46,7 @@
  * See com.android.server.display.DisplayViewport.
  */
 struct DisplayViewport {
-    int32_t displayId; // -1 if invalid
+    ui::LogicalDisplayId displayId;
     ui::Rotation orientation;
     int32_t logicalLeft;
     int32_t logicalTop;
@@ -67,7 +66,7 @@
     ViewportType type;
 
     DisplayViewport()
-          : displayId(ADISPLAY_ID_NONE),
+          : displayId(ui::LogicalDisplayId::INVALID),
             orientation(ui::ROTATION_0),
             logicalLeft(0),
             logicalTop(0),
@@ -99,12 +98,10 @@
         return !(*this == other);
     }
 
-    inline bool isValid() const {
-        return displayId >= 0;
-    }
+    inline bool isValid() const { return displayId.isValid(); }
 
     void setNonDisplayViewport(int32_t width, int32_t height) {
-        displayId = ADISPLAY_ID_NONE;
+        displayId = ui::LogicalDisplayId::INVALID;
         orientation = ui::ROTATION_0;
         logicalLeft = 0;
         logicalTop = 0;
@@ -123,12 +120,13 @@
     }
 
     std::string toString() const {
-        return StringPrintf("Viewport %s: displayId=%d, uniqueId=%s, port=%s, orientation=%d, "
+        return StringPrintf("Viewport %s: displayId=%s, uniqueId=%s, port=%s, orientation=%d, "
                             "logicalFrame=[%d, %d, %d, %d], "
                             "physicalFrame=[%d, %d, %d, %d], "
                             "deviceSize=[%d, %d], "
                             "isActive=[%d]",
-                            ftl::enum_string(type).c_str(), displayId, uniqueId.c_str(),
+                            ftl::enum_string(type).c_str(), displayId.toString().c_str(),
+                            uniqueId.c_str(),
                             physicalPort ? ftl::to_string(*physicalPort).c_str() : "<none>",
                             static_cast<int>(orientation), logicalLeft, logicalTop, logicalRight,
                             logicalBottom, physicalLeft, physicalTop, physicalRight, physicalBottom,
diff --git a/include/input/Input.h b/include/input/Input.h
index 1c4ea6b..3ca9c19 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -26,8 +26,10 @@
 #ifdef __linux__
 #include <android/os/IInputConstants.h>
 #endif
+#include <android/os/PointerIconType.h>
 #include <math.h>
 #include <stdint.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
 #include <utils/Timers.h>
@@ -39,13 +41,11 @@
  * Additional private constants not defined in ndk/ui/input.h.
  */
 enum {
-#ifdef __linux__
+
     /* This event was generated or modified by accessibility service. */
     AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT, // 0x800,
-#else
-    AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800,
-#endif
+            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+
     /* Signifies that the key is being predispatched */
     AKEY_EVENT_FLAG_PREDISPATCH = 0x20000000,
 
@@ -53,11 +53,11 @@
     AKEY_EVENT_FLAG_START_TRACKING = 0x40000000,
 
     /* Key event is inconsistent with previously sent key events. */
-    AKEY_EVENT_FLAG_TAINTED = 0x80000000,
+    AKEY_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
 };
 
 enum {
-
+    // AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED is defined in include/android/input.h
     /**
      * This flag indicates that the window that received this motion event is partly
      * or wholly obscured by another visible window above it.  This flag is set to true
@@ -68,13 +68,16 @@
      * to drop the suspect touches or to take additional precautions to confirm the user's
      * actual intent.
      */
-    AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2,
-
+    AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+    AMOTION_EVENT_FLAG_HOVER_EXIT_PENDING =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING,
     /**
      * This flag indicates that the event has been generated by a gesture generator. It
      * provides a hint to the GestureDetector to not apply any touch slop.
      */
-    AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8,
+    AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE,
 
     /**
      * This flag indicates that the event will not cause a focus change if it is directed to an
@@ -82,20 +85,20 @@
      * gestures to allow the user to direct gestures to an unfocused window without bringing it
      * into focus.
      */
-    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40,
+    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
 
-#if defined(__linux__)
     /**
      * This event was generated or modified by accessibility service.
      */
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT, // 0x800,
-#else
-    AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800,
-#endif
+            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+
+    AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS,
 
     /* Motion event is inconsistent with previously sent motion events. */
-    AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
+    AMOTION_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
 };
 
 /**
@@ -115,9 +118,10 @@
 /**
  * This flag indicates that the point up event has been canceled.
  * Typically this is used for palm event when the user has accidental touches.
- * TODO: Adjust flag to public api
+ * TODO(b/338143308): Add this to NDK
  */
-constexpr int32_t AMOTION_EVENT_FLAG_CANCELED = 0x20;
+constexpr int32_t AMOTION_EVENT_FLAG_CANCELED =
+        android::os::IInputConstants::INPUT_EVENT_FLAG_CANCELED;
 
 enum {
     /*
@@ -180,7 +184,8 @@
  * Declare a concrete type for the NDK's input event forward declaration.
  */
 struct AInputEvent {
-    virtual ~AInputEvent() { }
+    virtual ~AInputEvent() {}
+    bool operator==(const AInputEvent&) const = default;
 };
 
 /*
@@ -193,9 +198,7 @@
 
 namespace android {
 
-#ifdef __linux__
 class Parcel;
-#endif
 
 /*
  * Apply the given transform to the point without applying any translation/offset.
@@ -301,12 +304,8 @@
 
     POLICY_FLAG_RAW_MASK = 0x0000ffff,
 
-#ifdef __linux__
     POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY =
             android::os::IInputConstants::POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY,
-#else
-    POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000,
-#endif
 
     /* These flags are set by the input dispatcher. */
 
@@ -475,10 +474,8 @@
 
     vec2 getXYValue() const { return vec2(getX(), getY()); }
 
-#ifdef __linux__
     status_t readFromParcel(Parcel* parcel);
     status_t writeToParcel(Parcel* parcel) const;
-#endif
 
     bool operator==(const PointerCoords& other) const;
     inline bool operator!=(const PointerCoords& other) const {
@@ -515,6 +512,8 @@
     PointerProperties& operator=(const PointerProperties&) = default;
 };
 
+std::ostream& operator<<(std::ostream& out, const PointerProperties& properties);
+
 // TODO(b/211379801) : Use a strong type from ftl/mixins.h instead
 using DeviceId = int32_t;
 
@@ -535,16 +534,18 @@
 
     inline void setSource(uint32_t source) { mSource = source; }
 
-    inline int32_t getDisplayId() const { return mDisplayId; }
+    inline ui::LogicalDisplayId getDisplayId() const { return mDisplayId; }
 
-    inline void setDisplayId(int32_t displayId) { mDisplayId = displayId; }
+    inline void setDisplayId(ui::LogicalDisplayId displayId) { mDisplayId = displayId; }
 
     inline std::array<uint8_t, 32> getHmac() const { return mHmac; }
 
     static int32_t nextId();
 
+    bool operator==(const InputEvent&) const = default;
+
 protected:
-    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId,
                     std::array<uint8_t, 32> hmac);
 
     void initialize(const InputEvent& from);
@@ -552,7 +553,7 @@
     int32_t mId;
     DeviceId mDeviceId;
     uint32_t mSource;
-    int32_t mDisplayId;
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::INVALID};
     std::array<uint8_t, 32> mHmac;
 };
 
@@ -588,7 +589,7 @@
     static const char* getLabel(int32_t keyCode);
     static std::optional<int> getKeyCodeFromLabel(const char* label);
 
-    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId,
                     std::array<uint8_t, 32> hmac, int32_t action, int32_t flags, int32_t keyCode,
                     int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime,
                     nsecs_t eventTime);
@@ -596,6 +597,8 @@
 
     static const char* actionToString(int32_t action);
 
+    bool operator==(const KeyEvent&) const = default;
+
 protected:
     int32_t mAction;
     int32_t mFlags;
@@ -655,10 +658,6 @@
 
     inline void setActionButton(int32_t button) { mActionButton = button; }
 
-    inline float getXOffset() const { return mTransform.tx(); }
-
-    inline float getYOffset() const { return mTransform.ty(); }
-
     inline const ui::Transform& getTransform() const { return mTransform; }
 
     std::optional<ui::Rotation> getSurfaceRotation() const;
@@ -852,7 +851,7 @@
 
     ssize_t findPointerIndex(int32_t pointerId) const;
 
-    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId,
                     std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton,
                     int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState,
                     MotionClassification classification, const ui::Transform& transform,
@@ -863,12 +862,32 @@
 
     void copyFrom(const MotionEvent* other, bool keepHistory);
 
+    // Initialize this event by keeping only the pointers from "other" that are in splitPointerIds.
+    void splitFrom(const MotionEvent& other, std::bitset<MAX_POINTER_ID + 1> splitPointerIds,
+                   int32_t newEventId);
+
     void addSample(
             nsecs_t eventTime,
             const PointerCoords* pointerCoords);
 
     void offsetLocation(float xOffset, float yOffset);
 
+    /**
+     * Get the X offset of this motion event relative to the origin of the raw coordinate space.
+     *
+     * In practice, this is the delta that was added to the raw screen coordinates (i.e. in logical
+     * display space) to adjust for the absolute position of the containing windows and views.
+     */
+    float getRawXOffset() const;
+
+    /**
+     * Get the Y offset of this motion event relative to the origin of the raw coordinate space.
+     *
+     * In practice, this is the delta that was added to the raw screen coordinates (i.e. in logical
+     * display space) to adjust for the absolute position of the containing windows and views.
+     */
+    float getRawYOffset() const;
+
     void scale(float globalScaleFactor);
 
     // Set 3x3 perspective matrix transformation.
@@ -879,10 +898,8 @@
     // Matrix is in row-major form and compatible with SkMatrix.
     void applyTransform(const std::array<float, 9>& matrix);
 
-#ifdef __linux__
     status_t readFromParcel(Parcel* parcel);
     status_t writeToParcel(Parcel* parcel) const;
-#endif
 
     static bool isTouchEvent(uint32_t source, int32_t action);
     inline bool isTouchEvent() const {
@@ -903,6 +920,11 @@
 
     static std::string actionToString(int32_t action);
 
+    static std::tuple<int32_t /*action*/, std::vector<PointerProperties>,
+                      std::vector<PointerCoords>>
+    split(int32_t action, int32_t flags, int32_t historySize, const std::vector<PointerProperties>&,
+          const std::vector<PointerCoords>&, std::bitset<MAX_POINTER_ID + 1> splitPointerIds);
+
     // MotionEvent will transform various axes in different ways, based on the source. For
     // example, the x and y axes will not have any offsets/translations applied if it comes from a
     // relative mouse device (since SOURCE_RELATIVE_MOUSE is a non-pointer source). These methods
@@ -915,6 +937,9 @@
     // The rounding precision for transformed motion events.
     static constexpr float ROUNDING_PRECISION = 0.001f;
 
+    bool operator==(const MotionEvent&) const;
+    inline bool operator!=(const MotionEvent& o) const { return !(*this == o); };
+
 protected:
     int32_t mAction;
     int32_t mActionButton;
@@ -1033,7 +1058,7 @@
     DeviceId deviceId;
     nsecs_t eventTimeNanos;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
 };
 
 /**
@@ -1161,15 +1186,17 @@
  */
 struct PointerCaptureRequest {
 public:
-    inline PointerCaptureRequest() : enable(false), seq(0) {}
-    inline PointerCaptureRequest(bool enable, uint32_t seq) : enable(enable), seq(seq) {}
+    inline PointerCaptureRequest() : window(), seq(0) {}
+    inline PointerCaptureRequest(sp<IBinder> window, uint32_t seq) : window(window), seq(seq) {}
     inline bool operator==(const PointerCaptureRequest& other) const {
-        return enable == other.enable && seq == other.seq;
+        return window == other.window && seq == other.seq;
     }
-    explicit inline operator bool() const { return enable; }
+    inline bool isEnable() const { return window != nullptr; }
 
-    // True iff this is a request to enable Pointer Capture.
-    bool enable;
+    // The requesting window.
+    // If the request is to enable the capture, this is the input token of the window that requested
+    // pointer capture. Otherwise, this is nullptr.
+    sp<IBinder> window;
 
     // The sequence number for the request.
     uint32_t seq;
@@ -1180,43 +1207,41 @@
  *
  * Due to backwards compatibility and public api constraints, this is a duplicate (but type safe)
  * definition of PointerIcon.java.
- *
- * TODO(b/235023317) move this definition to an aidl and statically assign to the below java public
- * api values.
- *
- * WARNING: Keep these definitions in sync with
- * frameworks/base/core/java/android/view/PointerIcon.java
  */
 enum class PointerIconStyle : int32_t {
-    TYPE_CUSTOM = -1,
-    TYPE_NULL = 0,
-    TYPE_NOT_SPECIFIED = 1,
-    TYPE_ARROW = 1000,
-    TYPE_CONTEXT_MENU = 1001,
-    TYPE_HAND = 1002,
-    TYPE_HELP = 1003,
-    TYPE_WAIT = 1004,
-    TYPE_CELL = 1006,
-    TYPE_CROSSHAIR = 1007,
-    TYPE_TEXT = 1008,
-    TYPE_VERTICAL_TEXT = 1009,
-    TYPE_ALIAS = 1010,
-    TYPE_COPY = 1011,
-    TYPE_NO_DROP = 1012,
-    TYPE_ALL_SCROLL = 1013,
-    TYPE_HORIZONTAL_DOUBLE_ARROW = 1014,
-    TYPE_VERTICAL_DOUBLE_ARROW = 1015,
-    TYPE_TOP_RIGHT_DOUBLE_ARROW = 1016,
-    TYPE_TOP_LEFT_DOUBLE_ARROW = 1017,
-    TYPE_ZOOM_IN = 1018,
-    TYPE_ZOOM_OUT = 1019,
-    TYPE_GRAB = 1020,
-    TYPE_GRABBING = 1021,
-    TYPE_HANDWRITING = 1022,
+    TYPE_CUSTOM = static_cast<int32_t>(::android::os::PointerIconType::CUSTOM),
+    TYPE_NULL = static_cast<int32_t>(::android::os::PointerIconType::TYPE_NULL),
+    TYPE_NOT_SPECIFIED = static_cast<int32_t>(::android::os::PointerIconType::NOT_SPECIFIED),
+    TYPE_ARROW = static_cast<int32_t>(::android::os::PointerIconType::ARROW),
+    TYPE_CONTEXT_MENU = static_cast<int32_t>(::android::os::PointerIconType::CONTEXT_MENU),
+    TYPE_HAND = static_cast<int32_t>(::android::os::PointerIconType::HAND),
+    TYPE_HELP = static_cast<int32_t>(::android::os::PointerIconType::HELP),
+    TYPE_WAIT = static_cast<int32_t>(::android::os::PointerIconType::WAIT),
+    TYPE_CELL = static_cast<int32_t>(::android::os::PointerIconType::CELL),
+    TYPE_CROSSHAIR = static_cast<int32_t>(::android::os::PointerIconType::CROSSHAIR),
+    TYPE_TEXT = static_cast<int32_t>(::android::os::PointerIconType::TEXT),
+    TYPE_VERTICAL_TEXT = static_cast<int32_t>(::android::os::PointerIconType::VERTICAL_TEXT),
+    TYPE_ALIAS = static_cast<int32_t>(::android::os::PointerIconType::ALIAS),
+    TYPE_COPY = static_cast<int32_t>(::android::os::PointerIconType::COPY),
+    TYPE_NO_DROP = static_cast<int32_t>(::android::os::PointerIconType::NO_DROP),
+    TYPE_ALL_SCROLL = static_cast<int32_t>(::android::os::PointerIconType::ALL_SCROLL),
+    TYPE_HORIZONTAL_DOUBLE_ARROW =
+            static_cast<int32_t>(::android::os::PointerIconType::HORIZONTAL_DOUBLE_ARROW),
+    TYPE_VERTICAL_DOUBLE_ARROW =
+            static_cast<int32_t>(::android::os::PointerIconType::VERTICAL_DOUBLE_ARROW),
+    TYPE_TOP_RIGHT_DOUBLE_ARROW =
+            static_cast<int32_t>(::android::os::PointerIconType::TOP_RIGHT_DOUBLE_ARROW),
+    TYPE_TOP_LEFT_DOUBLE_ARROW =
+            static_cast<int32_t>(::android::os::PointerIconType::TOP_LEFT_DOUBLE_ARROW),
+    TYPE_ZOOM_IN = static_cast<int32_t>(::android::os::PointerIconType::ZOOM_IN),
+    TYPE_ZOOM_OUT = static_cast<int32_t>(::android::os::PointerIconType::ZOOM_OUT),
+    TYPE_GRAB = static_cast<int32_t>(::android::os::PointerIconType::GRAB),
+    TYPE_GRABBING = static_cast<int32_t>(::android::os::PointerIconType::GRABBING),
+    TYPE_HANDWRITING = static_cast<int32_t>(::android::os::PointerIconType::HANDWRITING),
 
-    TYPE_SPOT_HOVER = 2000,
-    TYPE_SPOT_TOUCH = 2001,
-    TYPE_SPOT_ANCHOR = 2002,
+    TYPE_SPOT_HOVER = static_cast<int32_t>(::android::os::PointerIconType::SPOT_HOVER),
+    TYPE_SPOT_TOUCH = static_cast<int32_t>(::android::os::PointerIconType::SPOT_TOUCH),
+    TYPE_SPOT_ANCHOR = static_cast<int32_t>(::android::os::PointerIconType::SPOT_ANCHOR),
 };
 
 } // namespace android
diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h
new file mode 100644
index 0000000..611478c
--- /dev/null
+++ b/include/input/InputConsumer.h
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2024 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
+
+/*
+ * Native input transport.
+ *
+ * The InputConsumer is used by the application to receive events from the input dispatcher.
+ */
+
+#include "InputTransport.h"
+
+namespace android {
+
+/*
+ * Consumes input events from an input channel.
+ */
+class InputConsumer {
+public:
+    /* Create a consumer associated with an input channel. */
+    explicit InputConsumer(const std::shared_ptr<InputChannel>& channel);
+    /* Create a consumer associated with an input channel, override resampling system property */
+    explicit InputConsumer(const std::shared_ptr<InputChannel>& channel,
+                           bool enableTouchResampling);
+
+    /* Destroys the consumer and releases its input channel. */
+    ~InputConsumer();
+
+    /* Gets the underlying input channel. */
+    inline std::shared_ptr<InputChannel> getChannel() { return mChannel; }
+
+    /* Consumes an input event from the input channel and copies its contents into
+     * an InputEvent object created using the specified factory.
+     *
+     * Tries to combine a series of move events into larger batches whenever possible.
+     *
+     * If consumeBatches is false, then defers consuming pending batched events if it
+     * is possible for additional samples to be added to them later.  Call hasPendingBatch()
+     * to determine whether a pending batch is available to be consumed.
+     *
+     * If consumeBatches is true, then events are still batched but they are consumed
+     * immediately as soon as the input channel is exhausted.
+     *
+     * The frameTime parameter specifies the time when the current display frame started
+     * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
+     *
+     * The returned sequence number is never 0 unless the operation failed.
+     *
+     * Returns OK on success.
+     * Returns WOULD_BLOCK if there is no event present.
+     * Returns DEAD_OBJECT if the channel's peer has been closed.
+     * Returns NO_MEMORY if the event could not be created.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime,
+                     uint32_t* outSeq, InputEvent** outEvent);
+
+    /* Sends a finished signal to the publisher to inform it that the message
+     * with the specified sequence number has finished being process and whether
+     * the message was handled by the consumer.
+     *
+     * Returns OK on success.
+     * Returns BAD_VALUE if seq is 0.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t sendFinishedSignal(uint32_t seq, bool handled);
+
+    status_t sendTimeline(int32_t inputEventId,
+                          std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+
+    /* Returns true if there is a pending batch.
+     *
+     * Should be called after calling consume() with consumeBatches == false to determine
+     * whether consume() should be called again later on with consumeBatches == true.
+     */
+    bool hasPendingBatch() const;
+
+    /* Returns the source of first pending batch if exist.
+     *
+     * Should be called after calling consume() with consumeBatches == false to determine
+     * whether consume() should be called again later on with consumeBatches == true.
+     */
+    int32_t getPendingBatchSource() const;
+
+    /* Returns true when there is *likely* a pending batch or a pending event in the channel.
+     *
+     * This is only a performance hint and may return false negative results. Clients should not
+     * rely on availability of the message based on the return value.
+     */
+    bool probablyHasInput() const;
+
+    std::string dump() const;
+
+private:
+    // True if touch resampling is enabled.
+    const bool mResampleTouch;
+
+    std::shared_ptr<InputChannel> mChannel;
+
+    // TODO(b/311142655): delete this temporary tracing after the ANR bug is fixed
+    const std::string mProcessingTraceTag;
+    const std::string mLifetimeTraceTag;
+    const int32_t mLifetimeTraceCookie;
+
+    // The current input message.
+    InputMessage mMsg;
+
+    // True if mMsg contains a valid input message that was deferred from the previous
+    // call to consume and that still needs to be handled.
+    bool mMsgDeferred;
+
+    // Batched motion events per device and source.
+    struct Batch {
+        std::vector<InputMessage> samples;
+    };
+    std::vector<Batch> mBatches;
+
+    // Touch state per device and source, only for sources of class pointer.
+    struct History {
+        nsecs_t eventTime;
+        BitSet32 idBits;
+        int32_t idToIndex[MAX_POINTER_ID + 1];
+        PointerCoords pointers[MAX_POINTERS];
+
+        void initializeFrom(const InputMessage& msg) {
+            eventTime = msg.body.motion.eventTime;
+            idBits.clear();
+            for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+                uint32_t id = msg.body.motion.pointers[i].properties.id;
+                idBits.markBit(id);
+                idToIndex[id] = i;
+                pointers[i].copyFrom(msg.body.motion.pointers[i].coords);
+            }
+        }
+
+        void initializeFrom(const History& other) {
+            eventTime = other.eventTime;
+            idBits = other.idBits; // temporary copy
+            for (size_t i = 0; i < other.idBits.count(); i++) {
+                uint32_t id = idBits.clearFirstMarkedBit();
+                int32_t index = other.idToIndex[id];
+                idToIndex[id] = index;
+                pointers[index].copyFrom(other.pointers[index]);
+            }
+            idBits = other.idBits; // final copy
+        }
+
+        const PointerCoords& getPointerById(uint32_t id) const { return pointers[idToIndex[id]]; }
+
+        bool hasPointerId(uint32_t id) const { return idBits.hasBit(id); }
+    };
+    struct TouchState {
+        int32_t deviceId;
+        int32_t source;
+        size_t historyCurrent;
+        size_t historySize;
+        History history[2];
+        History lastResample;
+
+        void initialize(int32_t incomingDeviceId, int32_t incomingSource) {
+            deviceId = incomingDeviceId;
+            source = incomingSource;
+            historyCurrent = 0;
+            historySize = 0;
+            lastResample.eventTime = 0;
+            lastResample.idBits.clear();
+        }
+
+        void addHistory(const InputMessage& msg) {
+            historyCurrent ^= 1;
+            if (historySize < 2) {
+                historySize += 1;
+            }
+            history[historyCurrent].initializeFrom(msg);
+        }
+
+        const History* getHistory(size_t index) const {
+            return &history[(historyCurrent + index) & 1];
+        }
+
+        bool recentCoordinatesAreIdentical(uint32_t id) const {
+            // Return true if the two most recently received "raw" coordinates are identical
+            if (historySize < 2) {
+                return false;
+            }
+            if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) {
+                return false;
+            }
+            float currentX = getHistory(0)->getPointerById(id).getX();
+            float currentY = getHistory(0)->getPointerById(id).getY();
+            float previousX = getHistory(1)->getPointerById(id).getX();
+            float previousY = getHistory(1)->getPointerById(id).getY();
+            if (currentX == previousX && currentY == previousY) {
+                return true;
+            }
+            return false;
+        }
+    };
+    std::vector<TouchState> mTouchStates;
+
+    // Chain of batched sequence numbers.  When multiple input messages are combined into
+    // a batch, we append a record here that associates the last sequence number in the
+    // batch with the previous one.  When the finished signal is sent, we traverse the
+    // chain to individually finish all input messages that were part of the batch.
+    struct SeqChain {
+        uint32_t seq;   // sequence number of batched input message
+        uint32_t chain; // sequence number of previous batched input message
+    };
+    std::vector<SeqChain> mSeqChains;
+
+    // The time at which each event with the sequence number 'seq' was consumed.
+    // This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
+    // This collection is populated when the event is received, and the entries are erased when the
+    // events are finished. It should not grow infinitely because if an event is not ack'd, ANR
+    // will be raised for that connection, and no further events will be posted to that channel.
+    std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
+
+    status_t consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime, uint32_t* outSeq,
+                          InputEvent** outEvent);
+    status_t consumeSamples(InputEventFactoryInterface* factory, Batch& batch, size_t count,
+                            uint32_t* outSeq, InputEvent** outEvent);
+
+    void updateTouchState(InputMessage& msg);
+    void resampleTouchState(nsecs_t frameTime, MotionEvent* event, const InputMessage* next);
+
+    ssize_t findBatch(int32_t deviceId, int32_t source) const;
+    ssize_t findTouchState(int32_t deviceId, int32_t source) const;
+
+    nsecs_t getConsumeTime(uint32_t seq) const;
+    void popConsumeTime(uint32_t seq);
+    status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
+
+    static void rewriteMessage(TouchState& state, InputMessage& msg);
+    static bool canAddSample(const Batch& batch, const InputMessage* msg);
+    static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
+
+    static bool isTouchResamplingEnabled();
+};
+
+} // namespace android
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
new file mode 100644
index 0000000..9e48b08
--- /dev/null
+++ b/include/input/InputConsumerNoResampling.h
@@ -0,0 +1,211 @@
+/**
+ * Copyright 2024 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 <utils/Looper.h>
+#include "InputTransport.h"
+
+namespace android {
+
+/**
+ * An interface to receive batched input events. Even if you don't want batching, you still have to
+ * use this interface, and some of the events will be batched if your implementation is slow to
+ * handle the incoming input.
+ */
+class InputConsumerCallbacks {
+public:
+    virtual ~InputConsumerCallbacks(){};
+    virtual void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) = 0;
+    virtual void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) = 0;
+    /**
+     * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents".
+     * If you don't want batching, then call "consumeBatchedInputEvents" immediately with
+     * std::nullopt frameTime to receive the pending motion event(s).
+     * @param pendingBatchSource the source of the pending batch.
+     */
+    virtual void onBatchedInputEventPending(int32_t pendingBatchSource) = 0;
+    virtual void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) = 0;
+    virtual void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) = 0;
+    virtual void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) = 0;
+    virtual void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) = 0;
+};
+
+/**
+ * Consumes input events from an input channel.
+ *
+ * This is a re-implementation of InputConsumer that does not have resampling at the current moment.
+ * A lot of the higher-level logic has been folded into this class, to make it easier to use.
+ * In the legacy class, InputConsumer, the consumption logic was partially handled in the jni layer,
+ * as well as various actions like adding the fd to the Choreographer.
+ *
+ * TODO(b/297226446): use this instead of "InputConsumer":
+ * - Add resampling to this class
+ * - Allow various resampling strategies to be specified
+ * - Delete the old "InputConsumer" and use this class instead, renaming it to "InputConsumer".
+ * - Add tracing
+ * - Update all tests to use the new InputConsumer
+ *
+ * This class is not thread-safe. We are currently allowing the constructor to run on any thread,
+ * but all of the remaining APIs should be invoked on the looper thread only.
+ */
+class InputConsumerNoResampling final {
+public:
+    explicit InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
+                                       sp<Looper> looper, InputConsumerCallbacks& callbacks);
+    ~InputConsumerNoResampling();
+
+    /**
+     * Must be called exactly once for each event received through the callbacks.
+     */
+    void finishInputEvent(uint32_t seq, bool handled);
+    void reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
+    /**
+     * If you want to consume all events immediately (disable batching), the you still must call
+     * this. For frameTime, use a std::nullopt.
+     * @param frameTime the time up to which consume the events. When there's double (or triple)
+     * buffering, you may want to not consume all events currently available, because you could be
+     * still working on an older frame, but there could already have been events that arrived that
+     * are more recent.
+     * @return whether any events were actually consumed
+     */
+    bool consumeBatchedInputEvents(std::optional<nsecs_t> frameTime);
+    /**
+     * Returns true when there is *likely* a pending batch or a pending event in the channel.
+     *
+     * This is only a performance hint and may return false negative results. Clients should not
+     * rely on availability of the message based on the return value.
+     */
+    bool probablyHasInput() const;
+
+    std::string getName() { return mChannel->getName(); }
+
+    std::string dump() const;
+
+private:
+    std::shared_ptr<InputChannel> mChannel;
+    sp<Looper> mLooper;
+    InputConsumerCallbacks& mCallbacks;
+
+    // Looper-related infrastructure
+    /**
+     * This class is needed to associate the function "handleReceiveCallback" with the provided
+     * looper. The callback sent to the looper is RefBase - based, so we can't just send a reference
+     * of this class directly to the looper.
+     */
+    class LooperEventCallback : public LooperCallback {
+    public:
+        LooperEventCallback(std::function<int(int events)> callback) : mCallback(callback) {}
+        int handleEvent(int /*fd*/, int events, void* /*data*/) override {
+            return mCallback(events);
+        }
+
+    private:
+        std::function<int(int events)> mCallback;
+    };
+    sp<LooperEventCallback> mCallback;
+    /**
+     * The actual code that executes when the looper encounters available data on the InputChannel.
+     */
+    int handleReceiveCallback(int events);
+    int mFdEvents;
+    void setFdEvents(int events);
+
+    void ensureCalledOnLooperThread(const char* func) const;
+
+    // Event-reading infrastructure
+    /**
+     * A fifo queue of events to be sent to the InputChannel. We can't send all InputMessages to
+     * the channel immediately when they are produced, because it's possible that the InputChannel
+     * is blocked (if the channel buffer is full). When that happens, we don't want to drop the
+     * events. Therefore, events should only be erased from the queue after they've been
+     * successfully written to the InputChannel.
+     */
+    std::queue<InputMessage> mOutboundQueue;
+    /**
+     * Try to send all of the events in mOutboundQueue over the InputChannel. Not all events might
+     * actually get sent, because it's possible that the channel is blocked.
+     */
+    void processOutboundEvents();
+
+    /**
+     * The time at which each event with the sequence number 'seq' was consumed.
+     * This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
+     * This collection is populated when the event is received, and the entries are erased when the
+     * events are finished. It should not grow infinitely because if an event is not ack'd, ANR
+     * will be raised for that connection, and no further events will be posted to that channel.
+     */
+    std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
+    /**
+     * Find and return the consumeTime associated with the provided sequence number. Crashes if
+     * the provided seq number is not found.
+     */
+    nsecs_t popConsumeTime(uint32_t seq);
+
+    // Event reading and processing
+    /**
+     * Read all of the available events from the InputChannel
+     */
+    std::vector<InputMessage> readAllMessages();
+
+    /**
+     * Send InputMessage to the corresponding InputConsumerCallbacks function.
+     * @param msg
+     */
+    void handleMessage(const InputMessage& msg) const;
+
+    // Batching
+    /**
+     * Batch messages that can be batched. When an unbatchable message is encountered, send it
+     * to the InputConsumerCallbacks immediately. If there are batches remaining,
+     * notify InputConsumerCallbacks.
+     */
+    void handleMessages(std::vector<InputMessage>&& messages);
+    /**
+     * Batched InputMessages, per deviceId.
+     * For each device, we are storing a queue of batched messages. These will all be collapsed into
+     * a single MotionEvent (up to a specific frameTime) when the consumer calls
+     * `consumeBatchedInputEvents`.
+     */
+    std::map<DeviceId, std::queue<InputMessage>> mBatches;
+    /**
+     * A map from a single sequence number to several sequence numbers. This is needed because of
+     * batching. When batching is enabled, a single MotionEvent will contain several samples. Each
+     * sample came from an individual InputMessage of Type::Motion, and therefore will have to be
+     * finished individually. Therefore, when the app calls "finish" on a (possibly batched)
+     * MotionEvent, we will need to check this map in case there are multiple sequence numbers
+     * associated with a single number that the app provided.
+     *
+     * For example:
+     * Suppose we received 4 InputMessage's of type Motion, with action MOVE:
+     * InputMessage(MOVE)   InputMessage(MOVE)   InputMessage(MOVE)   InputMessage(MOVE)
+     *    seq=10               seq=11               seq=12               seq=13
+     * The app consumed them all as a batch, which means that the app received a single MotionEvent
+     * with historySize=3 and seq = 10.
+     *
+     * This map will look like:
+     * {
+     *   10: [11, 12, 13],
+     * }
+     * So the sequence number 10 will have 3 other sequence numbers associated with it.
+     * When the app calls 'finish' for seq=10, we need to call 'finish' 4 times total, for sequence
+     * numbers 10, 11, 12, 13. The app is not aware of the sequence numbers of each sample inside
+     * the batched MotionEvent that it received.
+     */
+    std::map<uint32_t, std::vector<uint32_t>> mBatchedSequenceNumbers;
+};
+
+} // namespace android
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index b7751f7..7d8c19e 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -75,6 +75,17 @@
     bool operator!=(const InputDeviceIdentifier&) const = default;
 };
 
+/**
+ * Holds View related behaviors for an InputDevice.
+ */
+struct InputDeviceViewBehavior {
+    /**
+     * The smooth scroll behavior that applies for all source/axis, if defined by the device.
+     * Empty optional if the device has not specified the default smooth scroll behavior.
+     */
+    std::optional<bool> shouldSmoothScroll;
+};
+
 /* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */
 enum class InputDeviceSensorType : int32_t {
     ACCELEROMETER = ASENSOR_TYPE_ACCELEROMETER,
@@ -104,6 +115,8 @@
     ACCURACY_LOW = 1,
     ACCURACY_MEDIUM = 2,
     ACCURACY_HIGH = 3,
+
+    ftl_last = ACCURACY_HIGH,
 };
 
 enum class InputDeviceSensorReportingMode : int32_t {
@@ -117,8 +130,9 @@
     INPUT = 0,
     PLAYER_ID = 1,
     KEYBOARD_BACKLIGHT = 2,
+    KEYBOARD_MIC_MUTE = 3,
 
-    ftl_last = KEYBOARD_BACKLIGHT
+    ftl_last = KEYBOARD_MIC_MUTE
 };
 
 enum class InputDeviceLightCapability : uint32_t {
@@ -266,7 +280,8 @@
 
     void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                     const InputDeviceIdentifier& identifier, const std::string& alias,
-                    bool isExternal, bool hasMic, int32_t associatedDisplayId);
+                    bool isExternal, bool hasMic, ui::LogicalDisplayId associatedDisplayId,
+                    InputDeviceViewBehavior viewBehavior = {{}}, bool enabled = true);
 
     inline int32_t getId() const { return mId; }
     inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -298,6 +313,8 @@
         return mKeyboardLayoutInfo;
     }
 
+    inline const InputDeviceViewBehavior& getViewBehavior() const { return mViewBehavior; }
+
     inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) {
         mKeyCharacterMap = value;
     }
@@ -331,7 +348,10 @@
     }
     inline std::optional<InputDeviceUsiVersion> getUsiVersion() const { return mUsiVersion; }
 
-    inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; }
+    inline ui::LogicalDisplayId getAssociatedDisplayId() const { return mAssociatedDisplayId; }
+
+    inline void setEnabled(bool enabled) { mEnabled = enabled; }
+    inline bool isEnabled() const { return mEnabled; }
 
 private:
     int32_t mId;
@@ -346,7 +366,8 @@
     int32_t mKeyboardType;
     std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
     std::optional<InputDeviceUsiVersion> mUsiVersion;
-    int32_t mAssociatedDisplayId;
+    ui::LogicalDisplayId mAssociatedDisplayId{ui::LogicalDisplayId::INVALID};
+    bool mEnabled;
 
     bool mHasVibrator;
     bool mHasBattery;
@@ -359,6 +380,8 @@
     std::unordered_map<int32_t, InputDeviceLightInfo> mLights;
     /* Map from battery ID to battery info */
     std::unordered_map<int32_t, InputDeviceBatteryInfo> mBatteries;
+    /** The View related behaviors for the device. */
+    InputDeviceViewBehavior mViewBehavior;
 };
 
 /* Types of input device configuration files. */
diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h
index 2d23b97..25d35e9 100644
--- a/include/input/InputEventBuilders.h
+++ b/include/input/InputEventBuilders.h
@@ -18,7 +18,6 @@
 
 #include <android/input.h>
 #include <attestation/HmacKeyManager.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <utils/Timers.h> // for nsecs_t, systemTime
 
@@ -83,7 +82,7 @@
         return *this;
     }
 
-    MotionEventBuilder& displayId(int32_t displayId) {
+    MotionEventBuilder& displayId(ui::LogicalDisplayId displayId) {
         mDisplayId = displayId;
         return *this;
     }
@@ -118,6 +117,16 @@
         return *this;
     }
 
+    MotionEventBuilder& transform(ui::Transform t) {
+        mTransform = t;
+        return *this;
+    }
+
+    MotionEventBuilder& rawTransform(ui::Transform t) {
+        mRawTransform = t;
+        return *this;
+    }
+
     MotionEvent build() {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
@@ -134,12 +143,11 @@
         }
 
         MotionEvent event;
-        static const ui::Transform kIdentityTransform;
         event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
                          mAction, mActionButton, mFlags, /*edgeFlags=*/0, AMETA_NONE, mButtonState,
-                         MotionClassification::NONE, kIdentityTransform,
+                         MotionClassification::NONE, mTransform,
                          /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
-                         mRawYCursorPosition, kIdentityTransform, mDownTime, mEventTime,
+                         mRawYCursorPosition, mRawTransform, mDownTime, mEventTime,
                          mPointers.size(), pointerProperties.data(), pointerCoords.data());
         return event;
     }
@@ -150,12 +158,14 @@
     int32_t mSource;
     nsecs_t mDownTime;
     nsecs_t mEventTime;
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
     int32_t mActionButton{0};
     int32_t mButtonState{0};
     int32_t mFlags{0};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    ui::Transform mTransform;
+    ui::Transform mRawTransform;
 
     std::vector<PointerBuilder> mPointers;
 };
@@ -198,7 +208,7 @@
         return *this;
     }
 
-    KeyEventBuilder& displayId(int32_t displayId) {
+    KeyEventBuilder& displayId(ui::LogicalDisplayId displayId) {
         mDisplayId = displayId;
         return *this;
     }
@@ -237,7 +247,7 @@
     uint32_t mSource;
     nsecs_t mDownTime;
     nsecs_t mEventTime;
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
     uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
     int32_t mFlags{0};
     int32_t mKeyCode{AKEYCODE_UNKNOWN};
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 59b9495..6548810 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -35,18 +35,16 @@
 #include <android-base/result.h>
 #include <android-base/unique_fd.h>
 
+#include <android/os/InputChannelCore.h>
 #include <binder/IBinder.h>
-#include <binder/Parcelable.h>
 #include <input/Input.h>
 #include <input/InputVerifier.h>
 #include <sys/stat.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
 #include <utils/Errors.h>
-#include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-
 namespace android {
 class Parcel;
 
@@ -231,18 +229,15 @@
  * input messages across processes.  Each channel has a descriptive name for debugging purposes.
  *
  * Each endpoint has its own InputChannel object that specifies its file descriptor.
+ * For parceling, this relies on android::os::InputChannelCore, defined in aidl.
  *
  * The input channel is closed when all references to it are released.
  */
-class InputChannel : public Parcelable {
+class InputChannel : private android::os::InputChannelCore {
 public:
-    static std::unique_ptr<InputChannel> create(const std::string& name,
-                                                android::base::unique_fd fd, sp<IBinder> token);
-    InputChannel() = default;
-    InputChannel(const InputChannel& other)
-          : mName(other.mName), mFd(other.dupFd()), mToken(other.mToken){};
-    InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token);
-    ~InputChannel() override;
+    static std::unique_ptr<InputChannel> create(android::os::InputChannelCore&& parceledChannel);
+    ~InputChannel();
+
     /**
      * Create a pair of input channels.
      * The two returned input channels are equivalent, and are labeled as "server" and "client"
@@ -254,9 +249,8 @@
                                          std::unique_ptr<InputChannel>& outServerChannel,
                                          std::unique_ptr<InputChannel>& outClientChannel);
 
-    inline std::string getName() const { return mName; }
-    inline const android::base::unique_fd& getFd() const { return mFd; }
-    inline sp<IBinder> getToken() const { return mToken; }
+    inline std::string getName() const { return name; }
+    inline int getFd() const { return fd.get(); }
 
     /* Send a message to the other endpoint.
      *
@@ -283,13 +277,37 @@
      */
     status_t receiveMessage(InputMessage* msg);
 
+    /* Tells whether there is a message in the channel available to be received.
+     *
+     * This is only a performance hint and may return false negative results. Clients should not
+     * rely on availability of the message based on the return value.
+     */
+    bool probablyHasInput() const;
+
+    /* Wait until there is a message in the channel.
+     *
+     * The |timeout| specifies how long to block waiting for an input event to appear. Negative
+     * values are not allowed.
+     *
+     * In some cases returning before timeout expiration can happen without a message available.
+     * This could happen after the channel was closed on the other side. Another possible reason
+     * is incorrect setup of the channel.
+     */
+    void waitForMessage(std::chrono::milliseconds timeout) const;
+
     /* Return a new object that has a duplicate of this channel's fd. */
     std::unique_ptr<InputChannel> dup() const;
 
-    void copyTo(InputChannel& outChannel) const;
+    void copyTo(android::os::InputChannelCore& outChannel) const;
 
-    status_t readFromParcel(const android::Parcel* parcel) override;
-    status_t writeToParcel(android::Parcel* parcel) const override;
+    /**
+     * Similar to "copyTo", but it takes ownership of the provided InputChannel (and after this is
+     * called, it destroys it).
+     * @param from the InputChannel that should be converted to InputChannelCore
+     * @param outChannel the pre-allocated InputChannelCore to which to transfer the 'from' channel
+     */
+    static void moveChannel(std::unique_ptr<InputChannel> from,
+                            android::os::InputChannelCore& outChannel);
 
     /**
      * The connection token is used to identify the input connection, i.e.
@@ -305,26 +323,11 @@
      */
     sp<IBinder> getConnectionToken() const;
 
-    bool operator==(const InputChannel& inputChannel) const {
-        struct stat lhs, rhs;
-        if (fstat(mFd.get(), &lhs) != 0) {
-            return false;
-        }
-        if (fstat(inputChannel.getFd().get(), &rhs) != 0) {
-            return false;
-        }
-        // If file descriptors are pointing to same inode they are duplicated fds.
-        return inputChannel.getName() == getName() && inputChannel.getConnectionToken() == mToken &&
-                lhs.st_ino == rhs.st_ino;
-    }
-
 private:
-    base::unique_fd dupFd() const;
+    static std::unique_ptr<InputChannel> create(const std::string& name,
+                                                android::base::unique_fd fd, sp<IBinder> token);
 
-    std::string mName;
-    base::unique_fd mFd;
-
-    sp<IBinder> mToken;
+    InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token);
 };
 
 /*
@@ -339,7 +342,7 @@
     ~InputPublisher();
 
     /* Gets the underlying input channel. */
-    inline std::shared_ptr<InputChannel> getChannel() { return mChannel; }
+    inline InputChannel& getChannel() const { return *mChannel; }
 
     /* Publishes a key event to the input channel.
      *
@@ -350,9 +353,10 @@
      * Other errors probably indicate that the channel is broken.
      */
     status_t publishKeyEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source,
-                             int32_t displayId, std::array<uint8_t, 32> hmac, int32_t action,
-                             int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
-                             int32_t repeatCount, nsecs_t downTime, nsecs_t eventTime);
+                             ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac,
+                             int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+                             int32_t metaState, int32_t repeatCount, nsecs_t downTime,
+                             nsecs_t eventTime);
 
     /* Publishes a motion event to the input channel.
      *
@@ -363,9 +367,9 @@
      * Other errors probably indicate that the channel is broken.
      */
     status_t publishMotionEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source,
-                                int32_t displayId, std::array<uint8_t, 32> hmac, int32_t action,
-                                int32_t actionButton, int32_t flags, int32_t edgeFlags,
-                                int32_t metaState, int32_t buttonState,
+                                ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac,
+                                int32_t action, int32_t actionButton, int32_t flags,
+                                int32_t edgeFlags, int32_t metaState, int32_t buttonState,
                                 MotionClassification classification, const ui::Transform& transform,
                                 float xPrecision, float yPrecision, float xCursorPosition,
                                 float yCursorPosition, const ui::Transform& rawTransform,
@@ -448,229 +452,4 @@
     InputVerifier mInputVerifier;
 };
 
-/*
- * Consumes input events from an input channel.
- */
-class InputConsumer {
-public:
-    /* Create a consumer associated with an input channel. */
-    explicit InputConsumer(const std::shared_ptr<InputChannel>& channel);
-    /* Create a consumer associated with an input channel, override resampling system property */
-    explicit InputConsumer(const std::shared_ptr<InputChannel>& channel,
-                           bool enableTouchResampling);
-
-    /* Destroys the consumer and releases its input channel. */
-    ~InputConsumer();
-
-    /* Gets the underlying input channel. */
-    inline std::shared_ptr<InputChannel> getChannel() { return mChannel; }
-
-    /* Consumes an input event from the input channel and copies its contents into
-     * an InputEvent object created using the specified factory.
-     *
-     * Tries to combine a series of move events into larger batches whenever possible.
-     *
-     * If consumeBatches is false, then defers consuming pending batched events if it
-     * is possible for additional samples to be added to them later.  Call hasPendingBatch()
-     * to determine whether a pending batch is available to be consumed.
-     *
-     * If consumeBatches is true, then events are still batched but they are consumed
-     * immediately as soon as the input channel is exhausted.
-     *
-     * The frameTime parameter specifies the time when the current display frame started
-     * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
-     *
-     * The returned sequence number is never 0 unless the operation failed.
-     *
-     * Returns OK on success.
-     * Returns WOULD_BLOCK if there is no event present.
-     * Returns DEAD_OBJECT if the channel's peer has been closed.
-     * Returns NO_MEMORY if the event could not be created.
-     * Other errors probably indicate that the channel is broken.
-     */
-    status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime,
-                     uint32_t* outSeq, InputEvent** outEvent);
-
-    /* Sends a finished signal to the publisher to inform it that the message
-     * with the specified sequence number has finished being process and whether
-     * the message was handled by the consumer.
-     *
-     * Returns OK on success.
-     * Returns BAD_VALUE if seq is 0.
-     * Other errors probably indicate that the channel is broken.
-     */
-    status_t sendFinishedSignal(uint32_t seq, bool handled);
-
-    status_t sendTimeline(int32_t inputEventId,
-                          std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
-
-    /* Returns true if there is a pending batch.
-     *
-     * Should be called after calling consume() with consumeBatches == false to determine
-     * whether consume() should be called again later on with consumeBatches == true.
-     */
-    bool hasPendingBatch() const;
-
-    /* Returns the source of first pending batch if exist.
-     *
-     * Should be called after calling consume() with consumeBatches == false to determine
-     * whether consume() should be called again later on with consumeBatches == true.
-     */
-    int32_t getPendingBatchSource() const;
-
-    std::string dump() const;
-
-private:
-    // True if touch resampling is enabled.
-    const bool mResampleTouch;
-
-    std::shared_ptr<InputChannel> mChannel;
-
-    // The current input message.
-    InputMessage mMsg;
-
-    // True if mMsg contains a valid input message that was deferred from the previous
-    // call to consume and that still needs to be handled.
-    bool mMsgDeferred;
-
-    // Batched motion events per device and source.
-    struct Batch {
-        std::vector<InputMessage> samples;
-    };
-    std::vector<Batch> mBatches;
-
-    // Touch state per device and source, only for sources of class pointer.
-    struct History {
-        nsecs_t eventTime;
-        BitSet32 idBits;
-        int32_t idToIndex[MAX_POINTER_ID + 1];
-        PointerCoords pointers[MAX_POINTERS];
-
-        void initializeFrom(const InputMessage& msg) {
-            eventTime = msg.body.motion.eventTime;
-            idBits.clear();
-            for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
-                uint32_t id = msg.body.motion.pointers[i].properties.id;
-                idBits.markBit(id);
-                idToIndex[id] = i;
-                pointers[i].copyFrom(msg.body.motion.pointers[i].coords);
-            }
-        }
-
-        void initializeFrom(const History& other) {
-            eventTime = other.eventTime;
-            idBits = other.idBits; // temporary copy
-            for (size_t i = 0; i < other.idBits.count(); i++) {
-                uint32_t id = idBits.clearFirstMarkedBit();
-                int32_t index = other.idToIndex[id];
-                idToIndex[id] = index;
-                pointers[index].copyFrom(other.pointers[index]);
-            }
-            idBits = other.idBits; // final copy
-        }
-
-        const PointerCoords& getPointerById(uint32_t id) const {
-            return pointers[idToIndex[id]];
-        }
-
-        bool hasPointerId(uint32_t id) const {
-            return idBits.hasBit(id);
-        }
-    };
-    struct TouchState {
-        int32_t deviceId;
-        int32_t source;
-        size_t historyCurrent;
-        size_t historySize;
-        History history[2];
-        History lastResample;
-
-        void initialize(int32_t deviceId, int32_t source) {
-            this->deviceId = deviceId;
-            this->source = source;
-            historyCurrent = 0;
-            historySize = 0;
-            lastResample.eventTime = 0;
-            lastResample.idBits.clear();
-        }
-
-        void addHistory(const InputMessage& msg) {
-            historyCurrent ^= 1;
-            if (historySize < 2) {
-                historySize += 1;
-            }
-            history[historyCurrent].initializeFrom(msg);
-        }
-
-        const History* getHistory(size_t index) const {
-            return &history[(historyCurrent + index) & 1];
-        }
-
-        bool recentCoordinatesAreIdentical(uint32_t id) const {
-            // Return true if the two most recently received "raw" coordinates are identical
-            if (historySize < 2) {
-                return false;
-            }
-            if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) {
-                return false;
-            }
-            float currentX = getHistory(0)->getPointerById(id).getX();
-            float currentY = getHistory(0)->getPointerById(id).getY();
-            float previousX = getHistory(1)->getPointerById(id).getX();
-            float previousY = getHistory(1)->getPointerById(id).getY();
-            if (currentX == previousX && currentY == previousY) {
-                return true;
-            }
-            return false;
-        }
-    };
-    std::vector<TouchState> mTouchStates;
-
-    // Chain of batched sequence numbers.  When multiple input messages are combined into
-    // a batch, we append a record here that associates the last sequence number in the
-    // batch with the previous one.  When the finished signal is sent, we traverse the
-    // chain to individually finish all input messages that were part of the batch.
-    struct SeqChain {
-        uint32_t seq;   // sequence number of batched input message
-        uint32_t chain; // sequence number of previous batched input message
-    };
-    std::vector<SeqChain> mSeqChains;
-
-    // The time at which each event with the sequence number 'seq' was consumed.
-    // This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
-    // This collection is populated when the event is received, and the entries are erased when the
-    // events are finished. It should not grow infinitely because if an event is not ack'd, ANR
-    // will be raised for that connection, and no further events will be posted to that channel.
-    std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
-
-    status_t consumeBatch(InputEventFactoryInterface* factory,
-            nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
-    status_t consumeSamples(InputEventFactoryInterface* factory,
-            Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent);
-
-    void updateTouchState(InputMessage& msg);
-    void resampleTouchState(nsecs_t frameTime, MotionEvent* event,
-            const InputMessage *next);
-
-    ssize_t findBatch(int32_t deviceId, int32_t source) const;
-    ssize_t findTouchState(int32_t deviceId, int32_t source) const;
-
-    nsecs_t getConsumeTime(uint32_t seq) const;
-    void popConsumeTime(uint32_t seq);
-    status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
-
-    static void rewriteMessage(TouchState& state, InputMessage& msg);
-    static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg);
-    static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg);
-    static void initializeFocusEvent(FocusEvent* event, const InputMessage* msg);
-    static void initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg);
-    static void initializeDragEvent(DragEvent* event, const InputMessage* msg);
-    static void initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg);
-    static void addSample(MotionEvent* event, const InputMessage* msg);
-    static bool canAddSample(const Batch& batch, const InputMessage* msg);
-    static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
-
-    static bool isTouchResamplingEnabled();
-};
-
 } // namespace android
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index dfcf766..92d5ec4 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -19,9 +19,7 @@
 #include <stdint.h>
 #include <list>
 
-#ifdef __linux__
 #include <binder/IBinder.h>
-#endif
 
 #include <android-base/result.h>
 #include <input/Input.h>
@@ -144,13 +142,11 @@
     std::pair<int32_t /*keyCode*/, int32_t /*metaState*/> applyKeyBehavior(int32_t keyCode,
                                                                            int32_t metaState) const;
 
-#ifdef __linux__
     /* Reads a key map from a parcel. */
     static std::unique_ptr<KeyCharacterMap> readFromParcel(Parcel* parcel);
 
     /* Writes a key map to a parcel. */
     void writeToParcel(Parcel* parcel) const;
-#endif
 
     bool operator==(const KeyCharacterMap& other) const = default;
 
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 3b6e401..f715039 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <array>
 #include <cstdint>
 #include <memory>
 #include <mutex>
@@ -28,6 +29,7 @@
 #include <android/sysprop/InputProperties.sysprop.h>
 #include <input/Input.h>
 #include <input/MotionPredictorMetricsManager.h>
+#include <input/RingBuffer.h>
 #include <input/TfLiteMotionPredictor.h>
 #include <utils/Timers.h> // for nsecs_t
 
@@ -37,6 +39,31 @@
     return sysprop::InputProperties::enable_motion_prediction().value_or(true);
 }
 
+// Tracker to calculate jerk from motion position samples.
+class JerkTracker {
+public:
+    // Initialize the tracker. If normalizedDt is true, assume that each sample pushed has dt=1.
+    JerkTracker(bool normalizedDt);
+
+    // Add a position to the tracker and update derivative estimates.
+    void pushSample(int64_t timestamp, float xPos, float yPos);
+
+    // Reset JerkTracker for a new motion input.
+    void reset();
+
+    // Return last jerk calculation, if enough samples have been collected.
+    // Jerk is defined as the 3rd derivative of position (change in
+    // acceleration) and has the units of d^3p/dt^3.
+    std::optional<float> jerkMagnitude() const;
+
+private:
+    const bool mNormalizedDt;
+
+    RingBuffer<int64_t> mTimestamps{4};
+    std::array<float, 4> mXDerivatives{}; // [x, x', x'', x''']
+    std::array<float, 4> mYDerivatives{}; // [y, y', y'', y''']
+};
+
 /**
  * Given a set of MotionEvents for the current gesture, predict the motion. The returned MotionEvent
  * contains a set of samples in the future.
@@ -97,6 +124,11 @@
 
     std::unique_ptr<TfLiteMotionPredictorBuffers> mBuffers;
     std::optional<MotionEvent> mLastEvent;
+    // mJerkTracker assumes normalized dt = 1 between recorded samples because
+    // the underlying mModel input also assumes fixed-interval samples.
+    // Normalized dt as 1 is also used to correspond with the similar Jank
+    // implementation from the JetPack MotionPredictor implementation.
+    JerkTracker mJerkTracker{true};
 
     std::optional<MotionPredictorMetricsManager> mMetricsManager;
 
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 83fffa3..3470be4 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -117,11 +117,12 @@
 template <typename T>
 std::string dumpVector(const std::vector<T>& values,
                        std::string (*valueToString)(const T&) = constToString) {
-    std::string dump = valueToString(values[0]);
-    for (size_t i = 1; i < values.size(); i++) {
-        dump += ", " + valueToString(values[i]);
+    std::string out;
+    for (const auto& value : values) {
+        out += out.empty() ? "[" : ", ";
+        out += valueToString(value);
     }
-    return dump;
+    return out.empty() ? "[]" : (out + "]");
 }
 
 const char* toString(bool value);
diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h
index 2edc138..728a8e1 100644
--- a/include/input/TfLiteMotionPredictor.h
+++ b/include/input/TfLiteMotionPredictor.h
@@ -105,6 +105,11 @@
         // The noise floor for predictions.
         // Distances (r) less than this should be discarded as noise.
         float distanceNoiseFloor = 0;
+
+        // Low and high jerk thresholds (with normalized dt = 1) for predictions.
+        // High jerk means more predictions will be pruned, vice versa for low.
+        float lowJerk = 0;
+        float highJerk = 0;
     };
 
     // Creates a model from an encoded Flatbuffer model.
diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h
index b78f63e..7c58c87 100644
--- a/include/input/VelocityControl.h
+++ b/include/input/VelocityControl.h
@@ -16,7 +16,10 @@
 
 #pragma once
 
+#include <vector>
+
 #include <android-base/stringprintf.h>
+#include <input/AccelerationCurve.h>
 #include <input/Input.h>
 #include <input/VelocityTracker.h>
 #include <utils/Timers.h>
@@ -86,12 +89,7 @@
 class VelocityControl {
 public:
     VelocityControl();
-
-    /* Gets the various parameters. */
-    const VelocityControlParameters& getParameters() const;
-
-    /* Sets the various parameters. */
-    void setParameters(const VelocityControlParameters& parameters);
+    virtual ~VelocityControl() {}
 
     /* Resets the current movement counters to zero.
      * This has the effect of nullifying any acceleration. */
@@ -101,16 +99,55 @@
      * scaled / accelerated delta based on the current velocity. */
     void move(nsecs_t eventTime, float* deltaX, float* deltaY);
 
-private:
+protected:
+    virtual void scaleDeltas(float* deltaX, float* deltaY) = 0;
+
     // If no movements are received within this amount of time,
     // we assume the movement has stopped and reset the movement counters.
     static const nsecs_t STOP_TIME = 500 * 1000000; // 500 ms
 
-    VelocityControlParameters mParameters;
-
     nsecs_t mLastMovementTime;
     float mRawPositionX, mRawPositionY;
     VelocityTracker mVelocityTracker;
 };
 
+/**
+ * Velocity control using a simple acceleration curve where the acceleration factor increases
+ * linearly with movement speed, subject to minimum and maximum values.
+ */
+class SimpleVelocityControl : public VelocityControl {
+public:
+    /** Gets the various parameters. */
+    const VelocityControlParameters& getParameters() const;
+
+    /** Sets the various parameters. */
+    void setParameters(const VelocityControlParameters& parameters);
+
+protected:
+    virtual void scaleDeltas(float* deltaX, float* deltaY) override;
+
+private:
+    VelocityControlParameters mParameters;
+};
+
+/** Velocity control using a curve made up of multiple reciprocal segments. */
+class CurvedVelocityControl : public VelocityControl {
+public:
+    CurvedVelocityControl();
+
+    /** Sets the curve to be used for acceleration. */
+    void setCurve(const std::vector<AccelerationCurveSegment>& curve);
+
+    void setAccelerationEnabled(bool enabled);
+
+protected:
+    virtual void scaleDeltas(float* deltaX, float* deltaY) override;
+
+private:
+    const AccelerationCurveSegment& segmentForSpeed(float speedMmPerS);
+
+    bool mAccelerationEnabled = true;
+    std::vector<AccelerationCurveSegment> mCurveSegments;
+};
+
 } // namespace android
diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h
index 21a2877..222dac8 100644
--- a/include/input/VirtualInputDevice.h
+++ b/include/input/VirtualInputDevice.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -64,6 +64,8 @@
 
 class VirtualMouse : public VirtualInputDevice {
 public:
+    // Expose to share with VirtualStylus.
+    static const std::map<int, UinputAction> BUTTON_ACTION_MAPPING;
     VirtualMouse(android::base::unique_fd fd);
     virtual ~VirtualMouse() override;
     bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction,
@@ -74,12 +76,13 @@
                           std::chrono::nanoseconds eventTime);
 
 private:
-    static const std::map<int, UinputAction> BUTTON_ACTION_MAPPING;
     static const std::map<int, int> BUTTON_CODE_MAPPING;
 };
 
 class VirtualTouchscreen : public VirtualInputDevice {
 public:
+    // Expose to share with VirtualStylus.
+    static const std::map<int, UinputAction> TOUCH_ACTION_MAPPING;
     VirtualTouchscreen(android::base::unique_fd fd);
     virtual ~VirtualTouchscreen() override;
     // TODO(b/259554911): changing float parameters to int32_t.
@@ -88,9 +91,7 @@
                          std::chrono::nanoseconds eventTime);
 
 private:
-    static const std::map<int, UinputAction> TOUCH_ACTION_MAPPING;
     static const std::map<int, int> TOOL_TYPE_MAPPING;
-
     /* The set of active touch pointers on this device.
      * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual
      * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id
@@ -101,4 +102,24 @@
     bool handleTouchDown(int32_t pointerId, std::chrono::nanoseconds eventTime);
     bool handleTouchUp(int32_t pointerId, std::chrono::nanoseconds eventTime);
 };
+
+class VirtualStylus : public VirtualInputDevice {
+public:
+    VirtualStylus(android::base::unique_fd fd);
+    ~VirtualStylus() override;
+    bool writeMotionEvent(int32_t toolType, int32_t action, int32_t locationX, int32_t locationY,
+                          int32_t pressure, int32_t tiltX, int32_t tiltY,
+                          std::chrono::nanoseconds eventTime);
+    bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction,
+                          std::chrono::nanoseconds eventTime);
+
+private:
+    static const std::map<int, int> TOOL_TYPE_MAPPING;
+    static const std::map<int, int> BUTTON_CODE_MAPPING;
+    // True if the stylus is touching or hovering on the screen.
+    bool mIsStylusDown;
+    bool handleStylusDown(uint16_t tool, std::chrono::nanoseconds eventTime);
+    bool handleStylusUp(uint16_t tool, std::chrono::nanoseconds eventTime);
+};
+
 } // namespace android
diff --git a/include/powermanager/HalResult.h b/include/powermanager/HalResult.h
new file mode 100644
index 0000000..7fe3822
--- /dev/null
+++ b/include/powermanager/HalResult.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *                        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/binder_auto_utils.h>
+#include <android/binder_status.h>
+#include <android/hardware/power/1.0/IPower.h>
+#include <binder/Status.h>
+#include <hidl/HidlSupport.h>
+#include <string>
+
+namespace android::power {
+
+static bool checkUnsupported(const ndk::ScopedAStatus& ndkStatus) {
+    return ndkStatus.getExceptionCode() == EX_UNSUPPORTED_OPERATION ||
+            ndkStatus.getStatus() == STATUS_UNKNOWN_TRANSACTION;
+}
+
+static bool checkUnsupported(const binder::Status& status) {
+    return status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
+            status.transactionError() == UNKNOWN_TRANSACTION;
+}
+
+// Result of a call to the Power HAL wrapper, holding data if successful.
+template <typename T>
+class HalResult {
+public:
+    static HalResult<T> ok(T&& value) { return HalResult(std::forward<T>(value)); }
+    static HalResult<T> ok(T& value) { return HalResult<T>::ok(T{value}); }
+    static HalResult<T> failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); }
+    static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
+
+    static HalResult<T> fromStatus(const binder::Status& status, T&& data) {
+        if (checkUnsupported(status)) {
+            return HalResult<T>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<T>::ok(std::forward<T>(data));
+        }
+        return HalResult<T>::failed(std::string(status.toString8().c_str()));
+    }
+
+    static HalResult<T> fromStatus(const binder::Status& status, T& data) {
+        return HalResult<T>::fromStatus(status, T{data});
+    }
+
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& ndkStatus, T&& data) {
+        if (checkUnsupported(ndkStatus)) {
+            return HalResult<T>::unsupported();
+        }
+        if (ndkStatus.isOk()) {
+            return HalResult<T>::ok(std::forward<T>(data));
+        }
+        return HalResult<T>::failed(std::string(ndkStatus.getDescription()));
+    }
+
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& ndkStatus, T& data) {
+        return HalResult<T>::fromStatus(ndkStatus, T{data});
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T&& data) {
+        return ret.isOk() ? HalResult<T>::ok(std::forward<T>(data))
+                          : HalResult<T>::failed(ret.description());
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T& data) {
+        return HalResult<T>::fromReturn(ret, T{data});
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
+                                   T&& data) {
+        return ret.isOk() ? HalResult<T>::fromStatus(status, std::forward<T>(data))
+                          : HalResult<T>::failed(ret.description());
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
+                                   T& data) {
+        return HalResult<T>::fromReturn(ret, status, T{data});
+    }
+
+    // This will throw std::bad_optional_access if this result is not ok.
+    const T& value() const { return mValue.value(); }
+    bool isOk() const { return !mUnsupported && mValue.has_value(); }
+    bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
+    bool isUnsupported() const { return mUnsupported; }
+    const char* errorMessage() const { return mErrorMessage.c_str(); }
+
+private:
+    std::optional<T> mValue;
+    std::string mErrorMessage;
+    bool mUnsupported;
+
+    explicit HalResult(T&& value)
+          : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {}
+    explicit HalResult(std::string errorMessage, bool unsupported)
+          : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {}
+};
+
+// Empty result
+template <>
+class HalResult<void> {
+public:
+    static HalResult<void> ok() { return HalResult(); }
+    static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
+    static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
+
+    static HalResult<void> fromStatus(const binder::Status& status) {
+        if (checkUnsupported(status)) {
+            return HalResult<void>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<void>::ok();
+        }
+        return HalResult<void>::failed(std::string(status.toString8().c_str()));
+    }
+
+    static HalResult<void> fromStatus(const ndk::ScopedAStatus& ndkStatus) {
+        if (ndkStatus.isOk()) {
+            return HalResult<void>::ok();
+        }
+        if (checkUnsupported(ndkStatus)) {
+            return HalResult<void>::unsupported();
+        }
+        return HalResult<void>::failed(ndkStatus.getDescription());
+    }
+
+    template <typename R>
+    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
+        return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
+    }
+
+    bool isOk() const { return !mUnsupported && !mFailed; }
+    bool isFailed() const { return !mUnsupported && mFailed; }
+    bool isUnsupported() const { return mUnsupported; }
+    const char* errorMessage() const { return mErrorMessage.c_str(); }
+
+private:
+    std::string mErrorMessage;
+    bool mFailed;
+    bool mUnsupported;
+
+    explicit HalResult(bool unsupported = false)
+          : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {}
+    explicit HalResult(std::string errorMessage)
+          : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
+};
+} // namespace android::power
diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h
index 9e426d3..7e0bd5b 100644
--- a/include/powermanager/PowerHalController.h
+++ b/include/powermanager/PowerHalController.h
@@ -23,6 +23,7 @@
 #include <aidl/android/hardware/power/Mode.h>
 #include <android-base/thread_annotations.h>
 #include <powermanager/PowerHalWrapper.h>
+#include <powermanager/PowerHintSessionWrapper.h>
 
 namespace android {
 
@@ -38,6 +39,7 @@
 
     virtual std::unique_ptr<HalWrapper> connect();
     virtual void reset();
+    virtual int32_t getAidlVersion();
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -59,10 +61,17 @@
                                      int32_t durationMs) override;
     virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode,
                                     bool enabled) override;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                      int64_t durationNanos) override;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+            int64_t durationNanos) override;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
     virtual HalResult<int64_t> getHintSessionPreferredRate() override;
+    virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(
+            int tgid, int uid) override;
+    virtual HalResult<void> closeSessionChannel(int tgid, int uid) override;
 
 private:
     std::mutex mConnectedHalMutex;
@@ -75,7 +84,7 @@
 
     std::shared_ptr<HalWrapper> initHal();
     template <typename T>
-    HalResult<T> processHalResult(HalResult<T> result, const char* functionName);
+    HalResult<T> processHalResult(HalResult<T>&& result, const char* functionName);
 };
 
 // -------------------------------------------------------------------------------------------------
diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h
index cbbfa59..ab66336 100644
--- a/include/powermanager/PowerHalLoader.h
+++ b/include/powermanager/PowerHalLoader.h
@@ -36,6 +36,8 @@
     static sp<hardware::power::V1_1::IPower> loadHidlV1_1();
     static sp<hardware::power::V1_2::IPower> loadHidlV1_2();
     static sp<hardware::power::V1_3::IPower> loadHidlV1_3();
+    // Returns aidl interface version, or 0 if AIDL is not used
+    static int32_t getAidlVersion();
 
 private:
     static std::mutex gHalMutex;
@@ -48,6 +50,8 @@
     static sp<hardware::power::V1_0::IPower> loadHidlV1_0Locked()
             EXCLUSIVE_LOCKS_REQUIRED(gHalMutex);
 
+    static int32_t gAidlInterfaceVersion;
+
     PowerHalLoader() = delete;
     ~PowerHalLoader() = delete;
 };
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index 4e4a1b0..6e347a9 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -14,19 +14,25 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_POWERHALWRAPPER_H
-#define ANDROID_POWERHALWRAPPER_H
+#pragma once
 
 #include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/ChannelConfig.h>
 #include <aidl/android/hardware/power/IPower.h>
 #include <aidl/android/hardware/power/IPowerHintSession.h>
 #include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/SessionConfig.h>
 #include <android-base/thread_annotations.h>
 #include <android/hardware/power/1.1/IPower.h>
 #include <android/hardware/power/1.2/IPower.h>
 #include <android/hardware/power/1.3/IPower.h>
+#include <powermanager/HalResult.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+
 #include <binder/Status.h>
 
+#include <utility>
+
 namespace android {
 
 namespace power {
@@ -38,115 +44,6 @@
     OFF = 2,
 };
 
-// Result of a call to the Power HAL wrapper, holding data if successful.
-template <typename T>
-class HalResult {
-public:
-    static HalResult<T> ok(T value) { return HalResult(value); }
-    static HalResult<T> failed(std::string msg) {
-        return HalResult(std::move(msg), /* unsupported= */ false);
-    }
-    static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
-
-    static HalResult<T> fromStatus(const binder::Status& status, T data) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<T>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<T>::ok(data);
-        }
-        return HalResult<T>::failed(std::string(status.toString8().c_str()));
-    }
-
-    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T data) {
-        if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<T>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<T>::ok(data);
-        }
-        return HalResult<T>::failed(std::string(status.getDescription()));
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T data) {
-        return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed(ret.description());
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
-                                   T data) {
-        return ret.isOk() ? HalResult<T>::fromStatus(status, data)
-                          : HalResult<T>::failed(ret.description());
-    }
-
-    // This will throw std::bad_optional_access if this result is not ok.
-    const T& value() const { return mValue.value(); }
-    bool isOk() const { return !mUnsupported && mValue.has_value(); }
-    bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
-    bool isUnsupported() const { return mUnsupported; }
-    const char* errorMessage() const { return mErrorMessage.c_str(); }
-
-private:
-    std::optional<T> mValue;
-    std::string mErrorMessage;
-    bool mUnsupported;
-
-    explicit HalResult(T value)
-          : mValue(std::make_optional(value)), mErrorMessage(), mUnsupported(false) {}
-    explicit HalResult(std::string errorMessage, bool unsupported)
-          : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {}
-};
-
-// Empty result of a call to the Power HAL wrapper.
-template <>
-class HalResult<void> {
-public:
-    static HalResult<void> ok() { return HalResult(); }
-    static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
-    static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
-
-    static HalResult<void> fromStatus(const binder::Status& status) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<void>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<void>::ok();
-        }
-        return HalResult<void>::failed(std::string(status.toString8().c_str()));
-    }
-
-    static HalResult<void> fromStatus(const ndk::ScopedAStatus& status) {
-        if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<void>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<void>::ok();
-        }
-        return HalResult<void>::failed(std::string(status.getDescription()));
-    }
-
-    template <typename R>
-    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
-        return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
-    }
-
-    bool isOk() const { return !mUnsupported && !mFailed; }
-    bool isFailed() const { return !mUnsupported && mFailed; }
-    bool isUnsupported() const { return mUnsupported; }
-    const char* errorMessage() const { return mErrorMessage.c_str(); }
-
-private:
-    std::string mErrorMessage;
-    bool mFailed;
-    bool mUnsupported;
-
-    explicit HalResult(bool unsupported = false)
-          : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {}
-    explicit HalResult(std::string errorMessage)
-          : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
-};
-
 // Wrapper for Power HAL handlers.
 class HalWrapper {
 public:
@@ -155,10 +52,17 @@
     virtual HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                                      int32_t durationMs) = 0;
     virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) = 0;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                      int64_t durationNanos) = 0;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+            int64_t durationNanos) = 0;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) = 0;
     virtual HalResult<int64_t> getHintSessionPreferredRate() = 0;
+    virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
+                                                                                       int uid) = 0;
+    virtual HalResult<void> closeSessionChannel(int tgid, int uid) = 0;
 };
 
 // Empty Power HAL wrapper that ignores all api calls.
@@ -170,14 +74,24 @@
     HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                              int32_t durationMs) override;
     HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
     HalResult<int64_t> getHintSessionPreferredRate() override;
+    HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
+                                                                               int uid) override;
+    HalResult<void> closeSessionChannel(int tgid, int uid) override;
+
+protected:
+    virtual const char* getUnsupportedMessage();
 };
 
 // Wrapper for the HIDL Power HAL v1.0.
-class HidlHalWrapperV1_0 : public HalWrapper {
+class HidlHalWrapperV1_0 : public EmptyHalWrapper {
 public:
     explicit HidlHalWrapperV1_0(sp<hardware::power::V1_0::IPower> handleV1_0)
           : mHandleV1_0(std::move(handleV1_0)) {}
@@ -186,14 +100,11 @@
     HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                              int32_t durationMs) override;
     HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
-            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-            int64_t durationNanos) override;
-    HalResult<int64_t> getHintSessionPreferredRate() override;
 
 protected:
     const sp<hardware::power::V1_0::IPower> mHandleV1_0;
     virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data);
+    const char* getUnsupportedMessage();
 
 private:
     HalResult<void> setInteractive(bool enabled);
@@ -238,7 +149,7 @@
 };
 
 // Wrapper for the AIDL Power HAL.
-class AidlHalWrapper : public HalWrapper {
+class AidlHalWrapper : public EmptyHalWrapper {
 public:
     explicit AidlHalWrapper(std::shared_ptr<aidl::android::hardware::power::IPower> handle)
           : mHandle(std::move(handle)) {}
@@ -247,24 +158,33 @@
     HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                              int32_t durationMs) override;
     HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
+
     HalResult<int64_t> getHintSessionPreferredRate() override;
+    HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
+                                                                               int uid) override;
+    HalResult<void> closeSessionChannel(int tgid, int uid) override;
+
+protected:
+    const char* getUnsupportedMessage() override;
 
 private:
     // Control access to the boost and mode supported arrays.
     std::mutex mBoostMutex;
     std::mutex mModeMutex;
     std::shared_ptr<aidl::android::hardware::power::IPower> mHandle;
-    // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
-    // Need to increase the array size if more boost supported.
-    std::array<
-            std::atomic<HalSupport>,
-            static_cast<int32_t>(aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) +
-                    1>
+    std::array<HalSupport,
+               static_cast<int32_t>(
+                       *(ndk::enum_range<aidl::android::hardware::power::Boost>().end() - 1)) +
+                       1>
             mBoostSupportedArray GUARDED_BY(mBoostMutex) = {HalSupport::UNKNOWN};
-    std::array<std::atomic<HalSupport>,
+    std::array<HalSupport,
                static_cast<int32_t>(
                        *(ndk::enum_range<aidl::android::hardware::power::Mode>().end() - 1)) +
                        1>
@@ -274,5 +194,3 @@
 }; // namespace power
 
 }; // namespace android
-
-#endif // ANDROID_POWERHALWRAPPER_H
diff --git a/include/powermanager/PowerHintSessionWrapper.h b/include/powermanager/PowerHintSessionWrapper.h
new file mode 100644
index 0000000..ba6fe77
--- /dev/null
+++ b/include/powermanager/PowerHintSessionWrapper.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/ChannelConfig.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/SessionConfig.h>
+#include <android-base/thread_annotations.h>
+#include "HalResult.h"
+
+namespace android::power {
+
+// Wrapper for power hint sessions, which allows for better mocking,
+// support checking, and failure handling than using hint sessions directly
+class PowerHintSessionWrapper {
+public:
+    virtual ~PowerHintSessionWrapper() = default;
+    PowerHintSessionWrapper(
+            std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>&& session);
+    virtual HalResult<void> updateTargetWorkDuration(int64_t in_targetDurationNanos);
+    virtual HalResult<void> reportActualWorkDuration(
+            const std::vector<::aidl::android::hardware::power::WorkDuration>& in_durations);
+    virtual HalResult<void> pause();
+    virtual HalResult<void> resume();
+    virtual HalResult<void> close();
+    virtual HalResult<void> sendHint(::aidl::android::hardware::power::SessionHint in_hint);
+    virtual HalResult<void> setThreads(const std::vector<int32_t>& in_threadIds);
+    virtual HalResult<void> setMode(::aidl::android::hardware::power::SessionMode in_type,
+                                    bool in_enabled);
+    virtual HalResult<aidl::android::hardware::power::SessionConfig> getSessionConfig();
+
+private:
+    std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mSession;
+    int32_t mInterfaceVersion;
+};
+
+} // namespace android::power
\ No newline at end of file
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index d50c5f8..8c356d0 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -18,6 +18,7 @@
 #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 
 #include <stdint.h>
+#include <android/performance_hint.h>
 
 __BEGIN_DECLS
 
@@ -53,6 +54,35 @@
      * CPU resources to what was used previously, and must wake up if inactive.
      */
     CPU_LOAD_RESUME = 3,
+
+    /**
+     * This hint indicates an increase in GPU workload intensity. It means that
+     * this hint session needs extra GPU resources to meet the target duration.
+     * This hint must be sent before reporting the actual duration to the session.
+     */
+    GPU_LOAD_UP = 5,
+
+    /**
+     * This hint indicates a decrease in GPU workload intensity. It means that
+     * this hint session can reduce GPU resources and still meet the target duration.
+     */
+    GPU_LOAD_DOWN = 6,
+
+    /*
+     * This hint indicates an upcoming GPU workload that is completely changed and
+     * unknown. It means that the hint session should reset GPU resources to a known
+     * baseline to prepare for an arbitrary load, and must wake up if inactive.
+     */
+    GPU_LOAD_RESET = 7,
+};
+
+// Allows access to PowerHAL's SessionTags without needing to import its AIDL
+enum class SessionTag : int32_t {
+  OTHER = 0,
+  SURFACEFLINGER = 1,
+  HWUI = 2,
+  GAME = 3,
+  APP = 4,
 };
 
 /**
@@ -63,14 +93,22 @@
  * @return 0 on success
  *         EPIPE if communication with the system service has failed.
  */
-int APerformanceHint_sendHint(void* session, SessionHint hint);
+int APerformanceHint_sendHint(APerformanceHintSession* session, SessionHint hint);
 
 /**
  * Return the list of thread ids, this API should only be used for testing only.
  */
-int APerformanceHint_getThreadIds(void* aPerformanceHintSession,
+int APerformanceHint_getThreadIds(APerformanceHintSession* session,
                                   int32_t* const threadIds, size_t* const size);
 
+/**
+ * Creates a session with additional options
+ */
+APerformanceHintSession* APerformanceHint_createSessionInternal(APerformanceHintManager* manager,
+                                        const int32_t* threadIds, size_t size,
+                                        int64_t initialTargetWorkDurationNanos, SessionTag tag);
+
+
 __END_DECLS
 
 #endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index 1a9766d..319716e 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -14,6 +14,7 @@
 
 package {
     default_applicable_licenses: ["frameworks_native_libs_arect_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 // Added automatically by a large-scale-change
diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
index ce9cd1c..04b7186 100644
--- a/libs/battery/MultiStateCounter.h
+++ b/libs/battery/MultiStateCounter.h
@@ -31,6 +31,8 @@
 namespace android {
 namespace battery {
 
+#define REPORTED_INVALID_TIMESTAMP_DELTA_MS 60000
+
 typedef uint16_t state_t;
 
 template <class T>
@@ -60,6 +62,12 @@
 
     void setState(state_t state, time_t timestamp);
 
+    /**
+     * Copies the current state and accumulated times-in-state from the source. Resets
+     * the accumulated value.
+     */
+    void copyStatesFrom(const MultiStateCounter<T>& source);
+
     void setValue(state_t state, const T& value);
 
     /**
@@ -171,8 +179,12 @@
         if (timestamp >= lastStateChangeTimestamp) {
             states[currentState].timeInStateSinceUpdate += timestamp - lastStateChangeTimestamp;
         } else {
-            ALOGE("setState is called with an earlier timestamp: %lu, previous timestamp: %lu\n",
-                  (unsigned long)timestamp, (unsigned long)lastStateChangeTimestamp);
+            if (timestamp < lastStateChangeTimestamp - REPORTED_INVALID_TIMESTAMP_DELTA_MS) {
+                ALOGE("setState is called with an earlier timestamp: %lu, "
+                      "previous timestamp: %lu\n",
+                      (unsigned long)timestamp, (unsigned long)lastStateChangeTimestamp);
+            }
+
             // The accumulated durations have become unreliable. For example, if the timestamp
             // sequence was 1000, 2000, 1000, 3000, if we accumulated the positive deltas,
             // we would get 4000, which is greater than (last - first). This could lead to
@@ -187,6 +199,22 @@
 }
 
 template <class T>
+void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) {
+    if (stateCount != source.stateCount) {
+        ALOGE("State count mismatch: %u vs. %u\n", stateCount, source.stateCount);
+        return;
+    }
+
+    currentState = source.currentState;
+    for (int i = 0; i < stateCount; i++) {
+        states[i].timeInStateSinceUpdate = source.states[i].timeInStateSinceUpdate;
+        states[i].counter = emptyValue;
+    }
+    lastStateChangeTimestamp = source.lastStateChangeTimestamp;
+    lastUpdateTimestamp = source.lastUpdateTimestamp;
+}
+
+template <class T>
 void MultiStateCounter<T>::setValue(state_t state, const T& value) {
     states[state].counter = value;
 }
@@ -232,8 +260,10 @@
                     }
                 }
             } else if (timestamp < lastUpdateTimestamp) {
-                ALOGE("updateValue is called with an earlier timestamp: %lu, previous: %lu\n",
-                      (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp);
+                if (timestamp < lastUpdateTimestamp - REPORTED_INVALID_TIMESTAMP_DELTA_MS) {
+                    ALOGE("updateValue is called with an earlier timestamp: %lu, previous: %lu\n",
+                          (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp);
+                }
 
                 for (int i = 0; i < stateCount; i++) {
                     states[i].timeInStateSinceUpdate = 0;
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
index cb11a54..a51a38a 100644
--- a/libs/battery/MultiStateCounterTest.cpp
+++ b/libs/battery/MultiStateCounterTest.cpp
@@ -72,6 +72,22 @@
     EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
 }
 
+TEST_F(MultiStateCounterTest, copyStatesFrom) {
+    DoubleMultiStateCounter sourceCounter(3, 0);
+
+    sourceCounter.updateValue(0, 0);
+    sourceCounter.setState(1, 0);
+    sourceCounter.setState(2, 1000);
+
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.copyStatesFrom(sourceCounter);
+    testCounter.updateValue(6.0, 3000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
+}
+
 TEST_F(MultiStateCounterTest, setEnabled) {
     DoubleMultiStateCounter testCounter(3, 0);
     testCounter.updateValue(0, 0);
diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp
index 69b11c0..7b58046 100644
--- a/libs/binder/IBatteryStats.cpp
+++ b/libs/binder/IBatteryStats.cpp
@@ -66,14 +66,14 @@
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
         data.writeInt32(uid);
-        remote()->transact(NOTE_START_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_START_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteStopAudio(int uid) {
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
         data.writeInt32(uid);
-        remote()->transact(NOTE_STOP_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_STOP_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteResetVideo() {
@@ -85,7 +85,7 @@
     virtual void noteResetAudio() {
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
-        remote()->transact(NOTE_RESET_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_RESET_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteFlashlightOn(int uid) {
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 77b3275..3f70e8c 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -2977,14 +2977,15 @@
         return continueWrite(desired);
     }
 
+    releaseObjects();
+
     uint8_t* data = reallocZeroFree(mData, mDataCapacity, desired, mDeallocZero);
     if (!data && desired > mDataCapacity) {
+        LOG_ALWAYS_FATAL("out of memory");
         mError = NO_MEMORY;
         return NO_MEMORY;
     }
 
-    releaseObjects();
-
     if (data || desired == 0) {
         LOG_ALLOC("Parcel %p: restart from %zu to %zu capacity", this, mDataCapacity, desired);
         if (mDataCapacity > desired) {
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index cf59420..af280d3 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -267,11 +267,24 @@
     }
 }
 
+void ABBinder::addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& /* recipient */,
+                                 void* /* cookie */) {
+    LOG_ALWAYS_FATAL("Should not reach this. Can't linkToDeath local binders.");
+}
+
 ABpBinder::ABpBinder(const ::android::sp<::android::IBinder>& binder)
     : AIBinder(nullptr /*clazz*/), mRemote(binder) {
     LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 }
-ABpBinder::~ABpBinder() {}
+
+ABpBinder::~ABpBinder() {
+    for (auto& recip : mDeathRecipients) {
+        sp<AIBinder_DeathRecipient> strongRecip = recip.recipient.promote();
+        if (strongRecip) {
+            strongRecip->pruneThisTransferEntry(getBinder(), recip.cookie);
+        }
+    }
+}
 
 sp<AIBinder> ABpBinder::lookupOrCreateFromBinder(const ::android::sp<::android::IBinder>& binder) {
     if (binder == nullptr) {
@@ -310,6 +323,12 @@
     return ret;
 }
 
+void ABpBinder::addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                                  void* cookie) {
+    std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
+    mDeathRecipients.emplace_back(recipient, cookie);
+}
+
 struct AIBinder_Weak {
     wp<AIBinder> binder;
 };
@@ -435,6 +454,17 @@
     LOG_ALWAYS_FATAL_IF(onDied == nullptr, "onDied == nullptr");
 }
 
+void AIBinder_DeathRecipient::pruneThisTransferEntry(const sp<IBinder>& who, void* cookie) {
+    std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
+    mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(),
+                                          [&](const sp<TransferDeathRecipient>& tdr) {
+                                              auto tdrWho = tdr->getWho();
+                                              return tdrWho != nullptr && tdrWho.promote() == who &&
+                                                     cookie == tdr->getCookie();
+                                          }),
+                           mDeathRecipients.end());
+}
+
 void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() {
     mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(),
                                           [](const sp<TransferDeathRecipient>& tdr) {
@@ -448,6 +478,21 @@
 
     std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
 
+    if (mOnUnlinked && cookie &&
+        std::find_if(mDeathRecipients.begin(), mDeathRecipients.end(),
+                     [&cookie](android::sp<TransferDeathRecipient> recipient) {
+                         return recipient->getCookie() == cookie;
+                     }) != mDeathRecipients.end()) {
+        ALOGE("Attempting to AIBinder_linkToDeath with the same cookie with an onUnlink callback. "
+              "This will cause the onUnlinked callback to be called multiple times with the same "
+              "cookie, which is usually not intended.");
+    }
+    if (!mOnUnlinked && cookie) {
+        ALOGW("AIBinder_linkToDeath is being called with a non-null cookie and no onUnlink "
+              "callback set. This might not be intended. AIBinder_DeathRecipient_setOnUnlinked "
+              "should be called first.");
+    }
+
     sp<TransferDeathRecipient> recipient =
             new TransferDeathRecipient(binder, cookie, this, mOnDied, mOnUnlinked);
 
@@ -563,8 +608,11 @@
         return STATUS_UNEXPECTED_NULL;
     }
 
-    // returns binder_status_t
-    return recipient->linkToDeath(binder->getBinder(), cookie);
+    binder_status_t ret = recipient->linkToDeath(binder->getBinder(), cookie);
+    if (ret == STATUS_OK) {
+        binder->addDeathRecipient(recipient, cookie);
+    }
+    return ret;
 }
 
 binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index 9d5368f..f5b738c 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -51,6 +51,8 @@
         ::android::sp<::android::IBinder> binder = const_cast<AIBinder*>(this)->getBinder();
         return binder->remoteBinder() != nullptr;
     }
+    virtual void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                                   void* cookie) = 0;
 
    private:
     // AIBinder instance is instance of this class for a local object. In order to transact on a
@@ -78,6 +80,8 @@
     ::android::status_t dump(int fd, const ::android::Vector<::android::String16>& args) override;
     ::android::status_t onTransact(uint32_t code, const ::android::Parcel& data,
                                    ::android::Parcel* reply, binder_flags_t flags) override;
+    void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& /* recipient */,
+                           void* /* cookie */) override;
 
    private:
     ABBinder(const AIBinder_Class* clazz, void* userData);
@@ -106,12 +110,20 @@
 
     bool isServiceFuzzing() const { return mServiceFuzzing; }
     void setServiceFuzzing() { mServiceFuzzing = true; }
+    void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                           void* cookie) override;
 
    private:
     friend android::sp<ABpBinder>;
     explicit ABpBinder(const ::android::sp<::android::IBinder>& binder);
     ::android::sp<::android::IBinder> mRemote;
     bool mServiceFuzzing = false;
+    struct DeathRecipientInfo {
+        android::wp<AIBinder_DeathRecipient> recipient;
+        void* cookie;
+    };
+    std::mutex mDeathRecipientsMutex;
+    std::vector<DeathRecipientInfo> mDeathRecipients;
 };
 
 struct AIBinder_Class {
@@ -183,6 +195,7 @@
     binder_status_t linkToDeath(const ::android::sp<::android::IBinder>&, void* cookie);
     binder_status_t unlinkToDeath(const ::android::sp<::android::IBinder>& binder, void* cookie);
     void setOnUnlinked(AIBinder_DeathRecipient_onBinderUnlinked onUnlinked);
+    void pruneThisTransferEntry(const ::android::sp<::android::IBinder>&, void* cookie);
 
    private:
     // When the user of this API deletes a Bp object but not the death recipient, the
diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp
index 3ee36cd..ca92727 100644
--- a/libs/binder/ndk/tests/iface.cpp
+++ b/libs/binder/ndk/tests/iface.cpp
@@ -25,6 +25,7 @@
 
 const char* IFoo::kSomeInstanceName = "libbinder_ndk-test-IFoo";
 const char* IFoo::kInstanceNameToDieFor = "libbinder_ndk-test-IFoo-to-die";
+const char* IFoo::kInstanceNameToDieFor2 = "libbinder_ndk-test-IFoo-to-die2";
 const char* IFoo::kIFooDescriptor = "my-special-IFoo-class";
 
 struct IFoo_Class_Data {
diff --git a/libs/binder/ndk/tests/include/iface/iface.h b/libs/binder/ndk/tests/include/iface/iface.h
index 0a562f0..0cdd50b 100644
--- a/libs/binder/ndk/tests/include/iface/iface.h
+++ b/libs/binder/ndk/tests/include/iface/iface.h
@@ -27,6 +27,7 @@
    public:
     static const char* kSomeInstanceName;
     static const char* kInstanceNameToDieFor;
+    static const char* kInstanceNameToDieFor2;
     static const char* kIFooDescriptor;
 
     static AIBinder_Class* kClass;
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 966ec95..471ab0c 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -536,6 +536,7 @@
     bool deathReceived = false;
 
     std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
         std::cerr << "Binder died (as requested)." << std::endl;
         deathReceived = true;
         deathCv.notify_one();
@@ -547,6 +548,7 @@
     bool wasDeathReceivedFirst = false;
 
     std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
         std::cerr << "Binder unlinked (as requested)." << std::endl;
         wasDeathReceivedFirst = deathReceived;
         unlinkReceived = true;
@@ -560,7 +562,6 @@
 
     EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast<void*>(cookie)));
 
-    // the binder driver should return this if the service dies during the transaction
     EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
 
     foo = nullptr;
@@ -579,6 +580,173 @@
     binder = nullptr;
 }
 
+TEST(NdkBinder, DeathRecipientDropBinderNoDeath) {
+    using namespace std::chrono_literals;
+
+    std::mutex deathMutex;
+    std::condition_variable deathCv;
+    bool deathReceived = false;
+
+    std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        std::cerr << "Binder died (as requested)." << std::endl;
+        deathReceived = true;
+        deathCv.notify_one();
+    };
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    bool wasDeathReceivedFirst = false;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        std::cerr << "Binder unlinked (as requested)." << std::endl;
+        wasDeathReceivedFirst = deathReceived;
+        unlinkReceived = true;
+        unlinkCv.notify_one();
+    };
+
+    // keep the death recipient around
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink);
+
+    {
+        AIBinder* binder;
+        sp<IFoo> foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder);
+        ASSERT_NE(nullptr, foo.get());
+        ASSERT_NE(nullptr, binder);
+
+        DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+
+        EXPECT_EQ(STATUS_OK,
+                  AIBinder_linkToDeath(binder, recipient.get(), static_cast<void*>(cookie)));
+        // let the sp<IFoo> and AIBinder fall out of scope
+        AIBinder_decStrong(binder);
+        binder = nullptr;
+    }
+
+    {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        EXPECT_FALSE(deathCv.wait_for(lockDeath, 100ms, [&] { return deathReceived; }));
+        EXPECT_FALSE(deathReceived);
+    }
+
+    {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockUnlink, 1s, [&] { return unlinkReceived; }));
+        EXPECT_TRUE(unlinkReceived);
+        EXPECT_FALSE(wasDeathReceivedFirst);
+    }
+}
+
+TEST(NdkBinder, DeathRecipientDropBinderOnDied) {
+    using namespace std::chrono_literals;
+
+    std::mutex deathMutex;
+    std::condition_variable deathCv;
+    bool deathReceived = false;
+
+    sp<IFoo> foo;
+    AIBinder* binder;
+    std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        std::cerr << "Binder died (as requested)." << std::endl;
+        deathReceived = true;
+        AIBinder_decStrong(binder);
+        binder = nullptr;
+        deathCv.notify_one();
+    };
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    bool wasDeathReceivedFirst = false;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        std::cerr << "Binder unlinked (as requested)." << std::endl;
+        wasDeathReceivedFirst = deathReceived;
+        unlinkReceived = true;
+        unlinkCv.notify_one();
+    };
+
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink);
+
+    foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder);
+    ASSERT_NE(nullptr, foo.get());
+    ASSERT_NE(nullptr, binder);
+
+    DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+    EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient.get(), static_cast<void*>(cookie)));
+
+    EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
+
+    {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockDeath, 1s, [&] { return deathReceived; }));
+        EXPECT_TRUE(deathReceived);
+    }
+
+    {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockUnlink, 100ms, [&] { return unlinkReceived; }));
+        EXPECT_TRUE(unlinkReceived);
+        EXPECT_TRUE(wasDeathReceivedFirst);
+    }
+}
+
+void LambdaOnUnlinkMultiple(void* cookie) {
+    auto funcs = static_cast<DeathRecipientCookie*>(cookie);
+    (*funcs->onUnlink)();
+};
+
+TEST(NdkBinder, DeathRecipientMultipleLinks) {
+    using namespace std::chrono_literals;
+
+    ndk::SpAIBinder binder;
+    sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName, binder.getR());
+    ASSERT_NE(nullptr, foo.get());
+    ASSERT_NE(nullptr, binder);
+
+    std::function<void(void)> onDeath = [&] {};
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    constexpr uint32_t kNumberOfLinksToDeath = 4;
+    uint32_t countdown = kNumberOfLinksToDeath;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        countdown--;
+        if (countdown == 0) {
+            unlinkReceived = true;
+            unlinkCv.notify_one();
+        }
+    };
+
+    DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlinkMultiple);
+
+    for (int32_t i = 0; i < kNumberOfLinksToDeath; i++) {
+        EXPECT_EQ(STATUS_OK,
+                  AIBinder_linkToDeath(binder.get(), recipient.get(), static_cast<void*>(cookie)));
+    }
+
+    foo = nullptr;
+    binder = nullptr;
+
+    std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+    EXPECT_TRUE(unlinkCv.wait_for(lockUnlink, 5s, [&] { return unlinkReceived; }))
+            << "countdown: " << countdown;
+    EXPECT_TRUE(unlinkReceived);
+    EXPECT_EQ(countdown, 0);
+}
+
 TEST(NdkBinder, RetrieveNonNdkService) {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -958,6 +1126,10 @@
     }
     if (fork() == 0) {
         prctl(PR_SET_PDEATHSIG, SIGHUP);
+        return manualThreadPoolService(IFoo::kInstanceNameToDieFor2);
+    }
+    if (fork() == 0) {
+        prctl(PR_SET_PDEATHSIG, SIGHUP);
         return manualPollingService(IFoo::kSomeInstanceName);
     }
     if (fork() == 0) {
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 5c280f4..e378b86 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -115,6 +115,14 @@
         p.setDataPosition(pos);
         FUZZ_LOG() << "setDataPosition done";
     },
+    [] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
+        FUZZ_LOG() << "about to setData: " <<(bytes.data() ? HexString(bytes.data(), bytes.size()) : "null");
+        // TODO: allow all read and write operations
+        (*const_cast<::android::Parcel*>(&p)).setData(bytes.data(), bytes.size());
+        FUZZ_LOG() << "setData done";
+    },
     PARCEL_READ_NO_STATUS(size_t, allowFds),
     PARCEL_READ_NO_STATUS(size_t, hasFileDescriptors),
     PARCEL_READ_NO_STATUS(std::vector<android::sp<android::IBinder>>, debugReadAllStrongBinders),
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index 5b1e9ea..a57d07f 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -46,7 +46,18 @@
     (void)options;
 
     std::vector<uint8_t> input = provider.ConsumeRemainingBytes<uint8_t>();
-    p->setData(input.data(), input.size());
+
+    if (input.size() % 4 != 0) {
+        input.resize(input.size() + (sizeof(uint32_t) - input.size() % sizeof(uint32_t)));
+    }
+    CHECK_EQ(0, input.size() % 4);
+
+    p->setDataCapacity(input.size());
+    for (size_t i = 0; i < input.size(); i += 4) {
+        p->writeInt32(*((int32_t*)(input.data() + i)));
+    }
+
+    CHECK_EQ(0, memcmp(input.data(), p->data(), p->dataSize()));
 }
 static void fillRandomParcel(NdkParcelAdapter* p, FuzzedDataProvider&& provider,
                              RandomParcelOptions* options) {
diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index 2c25f74..196161b 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_library_headers {
diff --git a/libs/bufferstreams/Android.bp b/libs/bufferstreams/Android.bp
index 6c2a980..03ab31e 100644
--- a/libs/bufferstreams/Android.bp
+++ b/libs/bufferstreams/Android.bp
@@ -14,6 +14,7 @@
 
 package {
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 aconfig_declarations {
diff --git a/libs/bufferstreams/aidl/Android.bp b/libs/bufferstreams/aidl/Android.bp
new file mode 100644
index 0000000..3f1fa4e
--- /dev/null
+++ b/libs/bufferstreams/aidl/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2024 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.
+
+aidl_interface {
+    name: "android.graphics.bufferstreams",
+    unstable: true,
+    flags: ["-Werror"],
+    srcs: ["android/graphics/bufferstreams/*.aidl"],
+    headers: [
+        "HardwareBuffer_aidl",
+    ],
+    imports: [
+        "android.hardware.common-V2",
+    ],
+    backend: {
+        cpp: {
+            enabled: false,
+        },
+        java: {
+            enabled: false,
+        },
+        ndk: {
+            enabled: false,
+        },
+        rust: {
+            enabled: true,
+            additional_rustlibs: [
+                "libnativewindow_rs",
+            ],
+        },
+    },
+}
diff --git a/libs/bufferstreams/aidl/android/graphics/bufferstreams/BufferAttachment.aidl b/libs/bufferstreams/aidl/android/graphics/bufferstreams/BufferAttachment.aidl
new file mode 100644
index 0000000..5c905b1
--- /dev/null
+++ b/libs/bufferstreams/aidl/android/graphics/bufferstreams/BufferAttachment.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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 android.graphics.bufferstreams;
+
+import android.graphics.bufferstreams.IBufferOwner;
+import android.hardware.HardwareBuffer;
+
+// Single mapping between a buffer reference and heavy-weight data (like the
+// buffer itself) and data that is stable between frames.
+parcelable BufferAttachment {
+    // The HardwareBuffer itself.
+    //
+    // This field is @nullable for codegen, since HardwareBuffer doesn't implement Default in Rust.
+    // In practice, it should never be null.
+    @nullable HardwareBuffer buffer;
+    // The buffer owner to which this buffer should be returned.
+    IBufferOwner owner;
+}
diff --git a/libs/bufferstreams/aidl/android/graphics/bufferstreams/BufferCacheUpdate.aidl b/libs/bufferstreams/aidl/android/graphics/bufferstreams/BufferCacheUpdate.aidl
new file mode 100644
index 0000000..7504119
--- /dev/null
+++ b/libs/bufferstreams/aidl/android/graphics/bufferstreams/BufferCacheUpdate.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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 android.graphics.bufferstreams;
+
+import android.graphics.bufferstreams.BufferAttachment;
+
+// A event that changes the state downstream buffer caches. Clients are responsible for forwarding
+// these messages to their clients.
+union BufferCacheUpdate {
+    // Event requiring downstream caches to add new entries.
+    CacheBuffers cacheBuffers;
+    // Event requiring downstream caches to remove entries.
+    ForgetBuffers forgetBuffers;
+
+    parcelable CacheBuffers {
+        // Attachments to add.
+        List<BufferAttachment> attachments;
+    }
+
+    parcelable ForgetBuffers {
+        // References to remove.
+        long[] bufferIds;
+    }
+}
diff --git a/libs/bufferstreams/aidl/android/graphics/bufferstreams/Frame.aidl b/libs/bufferstreams/aidl/android/graphics/bufferstreams/Frame.aidl
new file mode 100644
index 0000000..1e0ec3b
--- /dev/null
+++ b/libs/bufferstreams/aidl/android/graphics/bufferstreams/Frame.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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 android.graphics.bufferstreams;
+
+import android.os.ParcelFileDescriptor;
+
+// A Frame represents a single buffer passing through the stream.
+parcelable Frame {
+    // The service must have provided an associated BufferAttachment and the client is required to
+    // maintain a cache between the two.
+    long bufferId;
+    // The expected present time of this frame, or -1 if immediate.
+    long presentTimeNs;
+    // The acquire fence of the buffer for this frame.
+    @nullable ParcelFileDescriptor fence;
+}
diff --git a/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferOwner.aidl b/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferOwner.aidl
new file mode 100644
index 0000000..8b25a62
--- /dev/null
+++ b/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferOwner.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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 android.graphics.bufferstreams;
+
+import android.os.ParcelFileDescriptor;
+
+// Interface from a client back to the owner of a buffer.
+interface IBufferOwner {
+    // Called when the buffer is done being processed by the stream to return its owner.
+    oneway void onBufferReleased(in long bufferId, in @nullable ParcelFileDescriptor releaseFence);
+}
diff --git a/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferSubscriber.aidl b/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferSubscriber.aidl
new file mode 100644
index 0000000..52e8216
--- /dev/null
+++ b/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferSubscriber.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 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 android.graphics.bufferstreams;
+
+import android.graphics.bufferstreams.BufferCacheUpdate;
+import android.graphics.bufferstreams.IBufferSubscription;
+import android.graphics.bufferstreams.Frame;
+
+// Interface provided by clients to a service, mirroring the non-IPC interface.
+//
+// Clients are required to maintain a local cache of Buffer IDs to BufferAttachments.
+interface IBufferSubscriber {
+    // Provide a BufferSubscription object which the client can use to request frames.
+    oneway void onSubscribe(in IBufferSubscription subscription);
+
+    // Notifies the client to update its local caches.
+    oneway void onBufferCacheUpdate(in BufferCacheUpdate update);
+
+    // Notifies the client that a requested frame is available.
+    oneway void onNext(in Frame frame);
+
+    // Notifies the client that a fatal error has occurred. No subsequent on_next events will be
+    // sent by the service.
+    //
+    // Clients must empty their caches.
+    oneway void onError();
+
+    // Notifies the client that no further on_next events will be sent by the service in response
+    // to it cancelling the subscription.
+    //
+    // Clients must empty their caches.
+    oneway void onComplete();
+}
diff --git a/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferSubscription.aidl b/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferSubscription.aidl
new file mode 100644
index 0000000..c37f4e6
--- /dev/null
+++ b/libs/bufferstreams/aidl/android/graphics/bufferstreams/IBufferSubscription.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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 android.graphics.bufferstreams;
+
+// Interface provided to a IBufferSubscriber to request frames or gracefully cancel their
+// subscription.
+interface IBufferSubscription {
+    // Request n more frames.
+    oneway void request(long n);
+    // Cancel the subscription. Requested frames may continue to arrive.
+    oneway void cancel();
+}
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
index bb573c5..5b3ec30 100644
--- a/libs/bufferstreams/examples/app/Android.bp
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -12,6 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
+}
+
 android_app {
     name: "BufferStreamsDemoApp",
     srcs: ["java/**/*.kt"],
diff --git a/libs/bufferstreams/examples/app/jni/Android.bp b/libs/bufferstreams/examples/app/jni/Android.bp
index 67910a1..003f4ed 100644
--- a/libs/bufferstreams/examples/app/jni/Android.bp
+++ b/libs/bufferstreams/examples/app/jni/Android.bp
@@ -12,6 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
+}
+
 cc_library_shared {
     name: "libbufferstreamdemoapp",
     cflags: [
diff --git a/libs/bufferstreams/rust/Android.bp b/libs/bufferstreams/rust/Android.bp
index 7fcb222..34feb5d 100644
--- a/libs/bufferstreams/rust/Android.bp
+++ b/libs/bufferstreams/rust/Android.bp
@@ -12,6 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
+}
+
 rust_defaults {
     name: "libbufferstreams_defaults",
     srcs: ["src/lib.rs"],
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_owner.rs b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
index a4abb9d..155a8bf 100644
--- a/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
+++ b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
@@ -16,7 +16,7 @@
 
 /// Trait that represents an owner of a buffer that might need to handle events such as a buffer
 /// being dropped.
-pub trait BufferOwner {
+pub trait BufferOwner: Send + Sync {
     /// Called when a buffer is dropped.
     fn on_return(&self, buffer: &Buffer);
 }
diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs
index be1525d..17d4d87 100644
--- a/libs/bufferstreams/rust/src/lib.rs
+++ b/libs/bufferstreams/rust/src/lib.rs
@@ -23,8 +23,6 @@
 use buffers::Buffer;
 pub use stream_config::*;
 
-use std::time::Instant;
-
 /// This function will print Hello World.
 #[no_mangle]
 pub extern "C" fn hello() -> bool {
@@ -106,7 +104,8 @@
 /// BufferSubscriptions serve as the bridge between BufferPublishers and
 /// BufferSubscribers. BufferSubscribers receive a BufferSubscription when they
 /// subscribe to a BufferPublisher via on_subscribe.
-/// This object is to be used by the BufferSubscriber to cancel its subscription
+///
+/// This object is used by the BufferSubscriber to cancel its subscription
 /// or request more buffers.
 ///
 /// BufferSubcriptions are required to adhere to the following, based on the
@@ -147,7 +146,7 @@
 /// no other Subscription exists at this point.
 /// * Calling Subscription.cancel MUST return normally.
 /// * Calling Subscription.request MUST return normally.
-pub trait BufferSubscription {
+pub trait BufferSubscription: Send + Sync + 'static {
     /// request
     fn request(&self, n: u64);
     /// cancel
@@ -161,8 +160,8 @@
 pub struct Frame {
     /// A buffer to be used this frame.
     pub buffer: Buffer,
-    /// The time at which the buffer was dispatched.
-    pub present_time: Instant,
+    /// The time at which this buffer is expected to be displayed.
+    pub present_time: i64,
     /// A fence used for reading/writing safely.
     pub fence: i32,
 }
@@ -175,14 +174,12 @@
     use anyhow::anyhow;
     use buffers::Buffer;
     use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
-    use std::borrow::BorrowMut;
-    use std::error::Error;
-    use std::ops::Add;
-    use std::sync::Arc;
-    use std::time::Duration;
+    use std::{borrow::BorrowMut, error::Error, ops::Add, sync::Arc};
 
-    use crate::publishers::testing::*;
-    use crate::subscribers::{testing::*, SharedSubscriber};
+    use crate::{
+        publishers::testing::*,
+        subscribers::{testing::*, SharedSubscriber},
+    };
 
     const STREAM_CONFIG: StreamConfig = StreamConfig {
         width: 1,
@@ -200,7 +197,7 @@
                     .create_hardware_buffer()
                     .expect("Unable to create hardware buffer for test"),
             ),
-            present_time: Instant::now() + Duration::from_secs(1),
+            present_time: 1,
             fence: 0,
         }
     }
diff --git a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
index c5c1fd3..82f528e 100644
--- a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
+++ b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use std::time::Instant;
-
 use crate::{
     buffers::BufferPool, subscriptions::SharedBufferSubscription, BufferPublisher,
     BufferSubscriber, Frame, StreamConfig,
@@ -41,7 +39,7 @@
 
     /// If the [SharedBufferSubscription] is ready for a [Frame], a buffer will be requested from
     /// [BufferPool] and sent over to the [BufferSubscriber].
-    pub fn send_next_frame(&mut self, present_time: Instant) -> bool {
+    pub fn send_next_frame(&mut self, present_time: i64) -> bool {
         if let Some(subscriber) = self.subscriber.as_mut() {
             if self.subscription.take_request() {
                 if let Some(buffer) = self.buffer_pool.next_buffer() {
@@ -101,7 +99,7 @@
 
         subscriber.map_inner(|s| s.request(1));
 
-        assert!(buffer_pool_publisher.send_next_frame(Instant::now()));
+        assert!(buffer_pool_publisher.send_next_frame(1));
 
         let events = subscriber.map_inner_mut(|s| s.take_events());
         assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 918680d..368f5e0 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -5,20 +5,26 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
     name: "ftl_test",
     test_suites: ["device-tests"],
+    header_libs: [
+        "libbase_headers",
+    ],
     srcs: [
         "algorithm_test.cpp",
         "cast_test.cpp",
         "concat_test.cpp",
         "enum_test.cpp",
+        "expected_test.cpp",
         "fake_guard_test.cpp",
         "flags_test.cpp",
         "function_test.cpp",
         "future_test.cpp",
+        "hash_test.cpp",
         "match_test.cpp",
         "mixins_test.cpp",
         "non_null_test.cpp",
@@ -35,5 +41,6 @@
         "-Wextra",
         "-Wpedantic",
         "-Wthread-safety",
+        "-Wno-gnu-statement-expression-from-macro-expansion",
     ],
 }
diff --git a/libs/ftl/algorithm_test.cpp b/libs/ftl/algorithm_test.cpp
index 487b1b8..11569f2 100644
--- a/libs/ftl/algorithm_test.cpp
+++ b/libs/ftl/algorithm_test.cpp
@@ -24,6 +24,17 @@
 namespace android::test {
 
 // Keep in sync with example usage in header file.
+TEST(Algorithm, Contains) {
+  const ftl::StaticVector vector = {1, 2, 3};
+  EXPECT_TRUE(ftl::contains(vector, 1));
+
+  EXPECT_FALSE(ftl::contains(vector, 0));
+  EXPECT_TRUE(ftl::contains(vector, 2));
+  EXPECT_TRUE(ftl::contains(vector, 3));
+  EXPECT_FALSE(ftl::contains(vector, 4));
+}
+
+// Keep in sync with example usage in header file.
 TEST(Algorithm, FindIf) {
   using namespace std::string_view_literals;
 
diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp
new file mode 100644
index 0000000..9b7f017
--- /dev/null
+++ b/libs/ftl/expected_test.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/expected.h>
+#include <ftl/optional.h>
+#include <ftl/unit.h>
+#include <gtest/gtest.h>
+
+#include <cctype>
+#include <string>
+#include <system_error>
+
+namespace android::test {
+
+using IntExp = ftl::Expected<int, std::errc>;
+using StringExp = ftl::Expected<std::string, std::errc>;
+
+using namespace std::string_literals;
+
+TEST(Expected, Construct) {
+  // Default value.
+  EXPECT_TRUE(IntExp().has_value());
+  EXPECT_EQ(IntExp(), IntExp(0));
+
+  EXPECT_TRUE(StringExp().has_value());
+  EXPECT_EQ(StringExp(), StringExp(""));
+
+  // Value.
+  ASSERT_TRUE(IntExp(42).has_value());
+  EXPECT_EQ(42, IntExp(42).value());
+
+  ASSERT_TRUE(StringExp("test").has_value());
+  EXPECT_EQ("test"s, StringExp("test").value());
+
+  // Error.
+  const auto exp = StringExp(ftl::Unexpected(std::errc::invalid_argument));
+  ASSERT_FALSE(exp.has_value());
+  EXPECT_EQ(std::errc::invalid_argument, exp.error());
+}
+
+TEST(Expected, HasError) {
+  EXPECT_FALSE(IntExp(123).has_error([](auto) { return true; }));
+  EXPECT_FALSE(IntExp(ftl::Unexpected(std::errc::io_error)).has_error([](auto) { return false; }));
+
+  EXPECT_TRUE(StringExp(ftl::Unexpected(std::errc::permission_denied)).has_error([](auto e) {
+    return e == std::errc::permission_denied;
+  }));
+}
+
+TEST(Expected, ValueOpt) {
+  EXPECT_EQ(ftl::Optional(-1), IntExp(-1).value_opt());
+  EXPECT_EQ(std::nullopt, IntExp(ftl::Unexpected(std::errc::broken_pipe)).value_opt());
+
+  {
+    const StringExp exp("foo"s);
+    EXPECT_EQ(ftl::Optional('f'),
+              exp.value_opt().transform([](const auto& s) { return s.front(); }));
+    EXPECT_EQ("foo"s, exp.value());
+  }
+  {
+    StringExp exp("foobar"s);
+    EXPECT_EQ(ftl::Optional(6), std::move(exp).value_opt().transform(&std::string::length));
+    EXPECT_TRUE(exp.value().empty());
+  }
+}
+
+namespace {
+
+IntExp increment(IntExp exp) {
+  const int i = FTL_TRY(exp);
+  return IntExp(i + 1);
+}
+
+StringExp repeat(StringExp exp) {
+  const std::string str = FTL_TRY(exp);
+  return StringExp(str + str);
+}
+
+void uppercase(char& c, ftl::Optional<char> opt) {
+  c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit())));
+}
+
+}  // namespace
+
+// Keep in sync with example usage in header file.
+TEST(Expected, Try) {
+  EXPECT_EQ(IntExp(100), increment(IntExp(99)));
+  EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::value_too_large)).has_error([](std::errc e) {
+    return e == std::errc::value_too_large;
+  }));
+
+  EXPECT_EQ(StringExp("haha"s), repeat(StringExp("ha"s)));
+  EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
+    return e == std::errc::bad_message;
+  }));
+
+  char c = '?';
+  uppercase(c, std::nullopt);
+  EXPECT_EQ(c, '?');
+
+  uppercase(c, 'a');
+  EXPECT_EQ(c, 'A');
+}
+
+}  // namespace android::test
diff --git a/libs/ftl/future_test.cpp b/libs/ftl/future_test.cpp
index 5a245b6..1140639 100644
--- a/libs/ftl/future_test.cpp
+++ b/libs/ftl/future_test.cpp
@@ -102,4 +102,42 @@
   decrement_thread.join();
 }
 
+TEST(Future, WaitFor) {
+  using namespace std::chrono_literals;
+  {
+    auto future = ftl::yield(42);
+    // Check that we can wait_for multiple times without invalidating the future
+    EXPECT_EQ(future.wait_for(1s), std::future_status::ready);
+    EXPECT_EQ(future.wait_for(1s), std::future_status::ready);
+    EXPECT_EQ(future.get(), 42);
+  }
+
+  {
+    std::condition_variable cv;
+    std::mutex m;
+    bool ready = false;
+
+    std::packaged_task<int32_t()> get_int([&] {
+      std::unique_lock lk(m);
+      cv.wait(lk, [&] { return ready; });
+      return 24;
+    });
+
+    auto get_future = ftl::Future(get_int.get_future());
+    std::thread get_thread(std::move(get_int));
+
+    EXPECT_EQ(get_future.wait_for(0s), std::future_status::timeout);
+    {
+      std::unique_lock lk(m);
+      ready = true;
+    }
+    cv.notify_one();
+
+    EXPECT_EQ(get_future.wait_for(1s), std::future_status::ready);
+    EXPECT_EQ(get_future.get(), 24);
+
+    get_thread.join();
+  }
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/hash_test.cpp b/libs/ftl/hash_test.cpp
new file mode 100644
index 0000000..9c7b8c2
--- /dev/null
+++ b/libs/ftl/hash_test.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/hash.h>
+#include <gtest/gtest.h>
+
+#include <numeric>
+#include <string>
+
+namespace android::test {
+
+TEST(Hash, StableHash) {
+  EXPECT_EQ(11160318154034397263ull, (ftl::stable_hash({})));
+
+  std::string string(64, '?');
+  std::iota(string.begin(), string.end(), 'A');
+
+  // Maximum length is 64 characters.
+  EXPECT_FALSE(ftl::stable_hash(string + '\n'));
+
+  EXPECT_EQ(6278090252846864564ull, ftl::stable_hash(std::string_view(string).substr(0, 8)));
+  EXPECT_EQ(1883356980931444616ull, ftl::stable_hash(std::string_view(string).substr(0, 16)));
+  EXPECT_EQ(8073093283835059304ull, ftl::stable_hash(std::string_view(string).substr(0, 32)));
+  EXPECT_EQ(18197365392429149980ull, ftl::stable_hash(string));
+}
+
+}  // namespace android::test
diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp
index bd0462b..367b398 100644
--- a/libs/ftl/non_null_test.cpp
+++ b/libs/ftl/non_null_test.cpp
@@ -14,12 +14,17 @@
  * limitations under the License.
  */
 
+#include <ftl/algorithm.h>
 #include <ftl/non_null.h>
 #include <gtest/gtest.h>
 
 #include <memory>
+#include <set>
 #include <string>
 #include <string_view>
+#include <type_traits>
+#include <unordered_set>
+#include <vector>
 
 namespace android::test {
 namespace {
@@ -47,7 +52,7 @@
 // Keep in sync with example usage in header file.
 TEST(NonNull, Example) {
   const auto string_ptr = ftl::as_non_null(std::make_shared<std::string>("android"));
-  std::size_t size;
+  std::size_t size{};
   get_length(string_ptr, ftl::as_non_null(&size));
   EXPECT_EQ(size, 7u);
 
@@ -71,5 +76,84 @@
 
 static_assert(longest(kApplePtr, kOrangePtr) == kOrangePtr);
 
+static_assert(static_cast<bool>(kApplePtr));
+
+static_assert(std::is_same_v<decltype(ftl::as_non_null(std::declval<const int* const>())),
+                             ftl::NonNull<const int*>>);
+
 }  // namespace
+
+TEST(NonNull, SwapRawPtr) {
+  int i1 = 123;
+  int i2 = 456;
+  auto ptr1 = ftl::as_non_null(&i1);
+  auto ptr2 = ftl::as_non_null(&i2);
+
+  std::swap(ptr1, ptr2);
+
+  EXPECT_EQ(*ptr1, 456);
+  EXPECT_EQ(*ptr2, 123);
+}
+
+TEST(NonNull, SwapSmartPtr) {
+  auto ptr1 = ftl::as_non_null(std::make_shared<int>(123));
+  auto ptr2 = ftl::as_non_null(std::make_shared<int>(456));
+
+  std::swap(ptr1, ptr2);
+
+  EXPECT_EQ(*ptr1, 456);
+  EXPECT_EQ(*ptr2, 123);
+}
+
+TEST(NonNull, VectorOfRawPtr) {
+  int i = 1;
+  std::vector<ftl::NonNull<int*>> vpi;
+  vpi.push_back(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(vpi, nullptr));
+  EXPECT_TRUE(ftl::contains(vpi, &i));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front()));
+}
+
+TEST(NonNull, VectorOfSmartPtr) {
+  std::vector<ftl::NonNull<std::shared_ptr<int>>> vpi;
+  vpi.push_back(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(vpi, nullptr));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front().get()));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front()));
+}
+
+TEST(NonNull, SetOfRawPtr) {
+  int i = 1;
+  std::set<ftl::NonNull<int*>> spi;
+  spi.insert(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, &i));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, SetOfSmartPtr) {
+  std::set<ftl::NonNull<std::shared_ptr<int>>> spi;
+  spi.insert(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, spi.begin()->get()));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, UnorderedSetOfRawPtr) {
+  int i = 1;
+  std::unordered_set<ftl::NonNull<int*>> spi;
+  spi.insert(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, &i));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, UnorderedSetOfSmartPtr) {
+  std::unordered_set<ftl::NonNull<std::shared_ptr<int>>> spi;
+  spi.insert(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, spi.begin()->get()));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp
index 91bf7bc..e7f1f14 100644
--- a/libs/ftl/optional_test.cpp
+++ b/libs/ftl/optional_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <ftl/expected.h>
 #include <ftl/optional.h>
 #include <ftl/static_vector.h>
 #include <ftl/string.h>
@@ -23,6 +24,7 @@
 #include <cstdlib>
 #include <functional>
 #include <numeric>
+#include <system_error>
 #include <utility>
 
 using namespace std::placeholders;
@@ -204,6 +206,19 @@
                      .or_else([] { return Optional(-1); }));
 }
 
+TEST(Optional, OkOr) {
+  using CharExp = ftl::Expected<char, std::errc>;
+  using StringExp = ftl::Expected<std::string, std::errc>;
+
+  EXPECT_EQ(CharExp('z'), Optional('z').ok_or(std::errc::broken_pipe));
+  EXPECT_EQ(CharExp(ftl::Unexpected(std::errc::broken_pipe)),
+            Optional<char>().ok_or(std::errc::broken_pipe));
+
+  EXPECT_EQ(StringExp("abc"s), Optional("abc"s).ok_or(std::errc::protocol_error));
+  EXPECT_EQ(StringExp(ftl::Unexpected(std::errc::protocol_error)),
+            Optional<std::string>().ok_or(std::errc::protocol_error));
+}
+
 // Comparison.
 namespace {
 
diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp
index 634877f..e96d70d 100644
--- a/libs/ftl/small_map_test.cpp
+++ b/libs/ftl/small_map_test.cpp
@@ -189,9 +189,20 @@
   }
 }
 
-TEST(SmallMap, TryEmplace) {
-  SmallMap<int, std::string, 3> map;
-  using Pair = decltype(map)::value_type;
+template <typename Capacity>
+struct SmallMapTest : testing::Test {
+  static constexpr std::size_t kCapacity = Capacity{}();
+};
+
+template <std::size_t N>
+using Capacity = std::integral_constant<std::size_t, N>;
+
+using Capacities = testing::Types<Capacity<3>, Capacity<0>>;
+TYPED_TEST_SUITE(SmallMapTest, Capacities, );
+
+TYPED_TEST(SmallMapTest, TryEmplace) {
+  SmallMap<int, std::string, TestFixture::kCapacity> map;
+  using Pair = typename decltype(map)::value_type;
 
   {
     const auto [it, ok] = map.try_emplace(123, "abc");
@@ -207,14 +218,22 @@
     const auto [it, ok] = map.try_emplace(-1);
     ASSERT_TRUE(ok);
     EXPECT_EQ(*it, Pair(-1, std::string()));
-    EXPECT_FALSE(map.dynamic());
+    if constexpr (map.static_capacity() > 0) {
+      EXPECT_FALSE(map.dynamic());
+    } else {
+      EXPECT_TRUE(map.dynamic());
+    }
   }
   {
     // Insertion fails if mapping exists.
     const auto [it, ok] = map.try_emplace(42, "!!!");
     EXPECT_FALSE(ok);
     EXPECT_EQ(*it, Pair(42, "???"));
-    EXPECT_FALSE(map.dynamic());
+    if constexpr (map.static_capacity() > 0) {
+      EXPECT_FALSE(map.dynamic());
+    } else {
+      EXPECT_TRUE(map.dynamic());
+    }
   }
   {
     // Insertion at capacity promotes the map.
@@ -240,9 +259,9 @@
 
 }  // namespace
 
-TEST(SmallMap, TryReplace) {
-  SmallMap<int, String, 3> map = ftl::init::map(1, "a")(2, "B");
-  using Pair = decltype(map)::value_type;
+TYPED_TEST(SmallMapTest, TryReplace) {
+  SmallMap<int, String, TestFixture::kCapacity> map = ftl::init::map(1, "a")(2, "B");
+  using Pair = typename decltype(map)::value_type;
 
   {
     // Replacing fails unless mapping exists.
@@ -260,7 +279,12 @@
     EXPECT_EQ(*it, Pair(2, "b"));
   }
 
-  EXPECT_FALSE(map.dynamic());
+  if constexpr (map.static_capacity() > 0) {
+    EXPECT_FALSE(map.dynamic());
+  } else {
+    EXPECT_TRUE(map.dynamic());
+  }
+
   EXPECT_TRUE(map.try_emplace(3, "abc").second);
   EXPECT_TRUE(map.try_emplace(4, "d").second);
   EXPECT_TRUE(map.dynamic());
@@ -284,9 +308,9 @@
   EXPECT_EQ(map, SmallMap(ftl::init::map(4, "d"s)(3, "c"s)(2, "b"s)(1, "a"s)));
 }
 
-TEST(SmallMap, EmplaceOrReplace) {
-  SmallMap<int, String, 3> map = ftl::init::map(1, "a")(2, "B");
-  using Pair = decltype(map)::value_type;
+TYPED_TEST(SmallMapTest, EmplaceOrReplace) {
+  SmallMap<int, String, TestFixture::kCapacity> map = ftl::init::map(1, "a")(2, "B");
+  using Pair = typename decltype(map)::value_type;
 
   {
     // New mapping is emplaced.
@@ -305,7 +329,12 @@
     EXPECT_EQ(*it, Pair(2, "b"));
   }
 
-  EXPECT_FALSE(map.dynamic());
+  if constexpr (map.static_capacity() > 0) {
+    EXPECT_FALSE(map.dynamic());
+  } else {
+    EXPECT_TRUE(map.dynamic());
+  }
+
   EXPECT_FALSE(map.emplace_or_replace(3, "abc").second);  // Replace.
   EXPECT_TRUE(map.emplace_or_replace(4, "d").second);     // Emplace.
   EXPECT_TRUE(map.dynamic());
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 6e2b803..8dabc2c 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_library {
diff --git a/libs/gralloc/types/fuzzer/Android.bp b/libs/gralloc/types/fuzzer/Android.bp
index 3c3b6af..8337182 100644
--- a/libs/gralloc/types/fuzzer/Android.bp
+++ b/libs/gralloc/types/fuzzer/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_fuzz {
diff --git a/libs/gralloc/types/tests/Android.bp b/libs/gralloc/types/tests/Android.bp
index 66eb0aa..b796c03 100644
--- a/libs/gralloc/types/tests/Android.bp
+++ b/libs/gralloc/types/tests/Android.bp
@@ -21,6 +21,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
@@ -30,5 +31,8 @@
         "libhidlbase",
     ],
     srcs: ["Gralloc4_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
diff --git a/libs/graphicsenv/GpuStatsInfo.cpp b/libs/graphicsenv/GpuStatsInfo.cpp
index 7b74214..33cebe3 100644
--- a/libs/graphicsenv/GpuStatsInfo.cpp
+++ b/libs/graphicsenv/GpuStatsInfo.cpp
@@ -96,6 +96,7 @@
     if ((status = parcel->writeUint64(vulkanDeviceFeaturesEnabled)) != OK) return status;
     if ((status = parcel->writeInt32Vector(vulkanInstanceExtensions)) != OK) return status;
     if ((status = parcel->writeInt32Vector(vulkanDeviceExtensions)) != OK) return status;
+    if ((status = parcel->writeUtf8VectorAsUtf16Vector(vulkanEngineNames)) != OK) return status;
 
     return OK;
 }
@@ -118,6 +119,7 @@
     if ((status = parcel->readUint64(&vulkanDeviceFeaturesEnabled)) != OK) return status;
     if ((status = parcel->readInt32Vector(&vulkanInstanceExtensions)) != OK) return status;
     if ((status = parcel->readInt32Vector(&vulkanDeviceExtensions)) != OK) return status;
+    if ((status = parcel->readUtf8VectorFromUtf16Vector(&vulkanEngineNames)) != OK) return status;
 
     return OK;
 }
@@ -161,6 +163,11 @@
         StringAppendF(&result, " 0x%x", extension);
     }
     result.append("\n");
+    result.append("vulkanEngineNames:");
+    for (const std::string& engineName : vulkanEngineNames) {
+        StringAppendF(&result, " %s,", engineName.c_str());
+    }
+    result.append("\n");
     return result;
 }
 
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 50c05f4..52383ac 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -445,6 +445,21 @@
                         extensionHashes, numStats);
 }
 
+void GraphicsEnv::addVulkanEngineName(const char* engineName) {
+    ATRACE_CALL();
+    if (engineName == nullptr) {
+        return;
+    }
+    std::lock_guard<std::mutex> lock(mStatsLock);
+    if (!readyToSendGpuStatsLocked()) return;
+
+    const sp<IGpuService> gpuService = getGpuService();
+    if (gpuService) {
+        gpuService->addVulkanEngineName(mGpuStats.appPackageName, mGpuStats.driverVersionCode,
+                                        engineName);
+    }
+}
+
 bool GraphicsEnv::readyToSendGpuStatsLocked() {
     // Only send stats for processes having at least one activity launched and that process doesn't
     // skip the GraphicsEnvironment setup.
diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp
index 5dc195c..42e7c37 100644
--- a/libs/graphicsenv/IGpuService.cpp
+++ b/libs/graphicsenv/IGpuService.cpp
@@ -77,6 +77,19 @@
                            IBinder::FLAG_ONEWAY);
     }
 
+    void addVulkanEngineName(const std::string& appPackageName, const uint64_t driverVersionCode,
+                             const char* engineName) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
+
+        data.writeUtf8AsUtf16(appPackageName);
+        data.writeUint64(driverVersionCode);
+        data.writeCString(engineName);
+
+        remote()->transact(BnGpuService::ADD_VULKAN_ENGINE_NAME, data, &reply,
+                           IBinder::FLAG_ONEWAY);
+    }
+
     void setUpdatableDriverPath(const std::string& driverPath) override {
         Parcel data, reply;
         data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
@@ -197,6 +210,21 @@
 
             return OK;
         }
+        case ADD_VULKAN_ENGINE_NAME: {
+            CHECK_INTERFACE(IGpuService, data, reply);
+
+            std::string appPackageName;
+            if ((status = data.readUtf8FromUtf16(&appPackageName)) != OK) return status;
+
+            uint64_t driverVersionCode;
+            if ((status = data.readUint64(&driverVersionCode)) != OK) return status;
+
+            const char* engineName;
+            if ((engineName = data.readCString()) == nullptr) return BAD_VALUE;
+
+            addVulkanEngineName(appPackageName, driverVersionCode, engineName);
+            return OK;
+        }
         case SET_UPDATABLE_DRIVER_PATH: {
             CHECK_INTERFACE(IGpuService, data, reply);
 
diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
index 9ebaf16..23f583b 100644
--- a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
+++ b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
@@ -60,6 +60,10 @@
 public:
     // This limits the worst case number of extensions to be tracked.
     static const uint32_t MAX_NUM_EXTENSIONS = 100;
+    // Max number of vulkan engine names for a single GpuStatsAppInfo
+    static const uint32_t MAX_VULKAN_ENGINE_NAMES = 16;
+    // Max length of a vulkan engine name string
+    static const size_t MAX_VULKAN_ENGINE_NAME_LENGTH = 50;
 
     GpuStatsAppInfo() = default;
     GpuStatsAppInfo(const GpuStatsAppInfo&) = default;
@@ -84,6 +88,7 @@
     uint64_t vulkanDeviceFeaturesEnabled = 0;
     std::vector<int32_t> vulkanInstanceExtensions = {};
     std::vector<int32_t> vulkanDeviceExtensions = {};
+    std::vector<std::string> vulkanEngineNames = {};
 
     std::chrono::time_point<std::chrono::system_clock> lastAccessTime;
 };
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index 6cce3f6..b0ab0b9 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -89,6 +89,8 @@
     // Set which device extensions are enabled for the app.
     void setVulkanDeviceExtensions(uint32_t enabledExtensionCount,
                                    const char* const* ppEnabledExtensionNames);
+    // Add the engine name passed in VkApplicationInfo during CreateInstance
+    void addVulkanEngineName(const char* engineName);
 
     /*
      * Api for Vk/GL layer injection.  Presently, drivers enable certain
diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h
index 45f05d6..a0d6e37 100644
--- a/libs/graphicsenv/include/graphicsenv/IGpuService.h
+++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h
@@ -46,6 +46,8 @@
                                      const uint64_t driverVersionCode,
                                      const GpuStatsInfo::Stats stats, const uint64_t* values,
                                      const uint32_t valueCount) = 0;
+    virtual void addVulkanEngineName(const std::string& appPackageName,
+                                     const uint64_t driverVersionCode, const char* engineName) = 0;
 
     // setter and getter for updatable driver path.
     virtual void setUpdatableDriverPath(const std::string& driverPath) = 0;
@@ -64,6 +66,7 @@
         GET_UPDATABLE_DRIVER_PATH,
         TOGGLE_ANGLE_AS_SYSTEM_DRIVER,
         SET_TARGET_STATS_ARRAY,
+        ADD_VULKAN_ENGINE_NAME,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 059e19e..2547297 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -18,6 +18,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 aconfig_declarations {
@@ -86,6 +87,7 @@
         "android/gui/DropInputMode.aidl",
         "android/gui/StalledTransactionInfo.aidl",
         "android/**/TouchOcclusionMode.aidl",
+        "android/gui/TrustedOverlay.aidl",
     ],
 }
 
@@ -154,9 +156,9 @@
     },
 }
 
-aidl_library {
-    name: "libgui_aidl_hdrs",
-    hdrs: [
+filegroup {
+    name: "libgui_extra_aidl_files",
+    srcs: [
         "android/gui/DisplayInfo.aidl",
         "android/gui/FocusRequest.aidl",
         "android/gui/InputApplicationInfo.aidl",
@@ -169,11 +171,34 @@
     ],
 }
 
+filegroup {
+    name: "libgui_extra_unstructured_aidl_files",
+    srcs: [
+        "android/gui/DisplayInfo.aidl",
+        "android/gui/InputApplicationInfo.aidl",
+        "android/gui/WindowInfo.aidl",
+        "android/gui/WindowInfosUpdate.aidl",
+    ],
+}
+
+aidl_library {
+    name: "libgui_aidl_hdrs",
+    hdrs: [":libgui_extra_aidl_files"],
+}
+
+aidl_library {
+    name: "libgui_extra_unstructured_aidl_hdrs",
+    hdrs: [":libgui_extra_unstructured_aidl_files"],
+}
+
 aidl_library {
     name: "libgui_aidl",
     srcs: ["aidl/**/*.aidl"],
     strip_import_prefix: "aidl",
-    deps: ["libgui_aidl_hdrs"],
+    deps: [
+        "libgui_aidl_hdrs",
+        "libgui_extra_unstructured_aidl_hdrs",
+    ],
 }
 
 filegroup {
@@ -240,7 +265,6 @@
         "IProducerListener.cpp",
         "ISurfaceComposer.cpp",
         "ITransactionCompletedListener.cpp",
-        "LayerDebugInfo.cpp",
         "LayerMetadata.cpp",
         "LayerStatePermissions.cpp",
         "LayerState.cpp",
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 19693e3..69345a9 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -423,6 +423,11 @@
     sp<IConsumerListener> listener;
     bool callOnFrameDequeued = false;
     uint64_t bufferId = 0; // Only used if callOnFrameDequeued == true
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    std::vector<gui::AdditionalOptions> allocOptions;
+    uint32_t allocOptionsGenId = 0;
+#endif
+
     { // Autolock scope
         std::unique_lock<std::mutex> lock(mCore->mMutex);
 
@@ -486,11 +491,17 @@
         }
 
         const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
-        if (mCore->mSharedBufferSlot == found &&
-                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) {
-            BQ_LOGE("dequeueBuffer: cannot re-allocate a shared"
-                    "buffer");
 
+        bool needsReallocation = buffer == nullptr ||
+                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        needsReallocation |= mSlots[found].mAdditionalOptionsGenerationId !=
+                mCore->mAdditionalOptionsGenerationId;
+#endif
+
+        if (mCore->mSharedBufferSlot == found && needsReallocation) {
+            BQ_LOGE("dequeueBuffer: cannot re-allocate a shared buffer");
             return BAD_VALUE;
         }
 
@@ -505,9 +516,7 @@
 
         mSlots[found].mBufferState.dequeue();
 
-        if ((buffer == nullptr) ||
-                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))
-        {
+        if (needsReallocation) {
             if (CC_UNLIKELY(ATRACE_ENABLED())) {
                 if (buffer == nullptr) {
                     ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.c_str());
@@ -530,6 +539,10 @@
             mSlots[found].mFence = Fence::NO_FENCE;
             mCore->mBufferAge = 0;
             mCore->mIsAllocating = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptions = mCore->mAdditionalOptions;
+            allocOptionsGenId = mCore->mAdditionalOptionsGenerationId;
+#endif
 
             returnFlags |= BUFFER_NEEDS_REALLOCATION;
         } else {
@@ -575,9 +588,29 @@
 
     if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
         BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<GraphicBufferAllocator::AdditionalOptions> tempOptions;
+        tempOptions.reserve(allocOptions.size());
+        for (const auto& it : allocOptions) {
+            tempOptions.emplace_back(it.name.c_str(), it.value);
+        }
+        const GraphicBufferAllocator::AllocationRequest allocRequest = {
+                .importBuffer = true,
+                .width = width,
+                .height = height,
+                .format = format,
+                .layerCount = BQ_LAYER_COUNT,
+                .usage = usage,
+                .requestorName = {mConsumerName.c_str(), mConsumerName.size()},
+                .extras = std::move(tempOptions),
+        };
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+#else
         sp<GraphicBuffer> graphicBuffer =
                 new GraphicBuffer(width, height, format, BQ_LAYER_COUNT, usage,
                                   {mConsumerName.c_str(), mConsumerName.size()});
+#endif
 
         status_t error = graphicBuffer->initCheck();
 
@@ -587,6 +620,9 @@
             if (error == NO_ERROR && !mCore->mIsAbandoned) {
                 graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
                 mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                mSlots[*outSlot].mAdditionalOptionsGenerationId = allocOptionsGenId;
+#endif
                 callOnFrameDequeued = true;
                 bufferId = mSlots[*outSlot].mGraphicBuffer->getId();
             }
@@ -887,6 +923,9 @@
     int callbackTicket = 0;
     uint64_t currentFrameNumber = 0;
     BufferItem item;
+    int connectedApi;
+    sp<Fence> lastQueuedFence;
+
     { // Autolock scope
         std::lock_guard<std::mutex> lock(mCore->mMutex);
 
@@ -1056,6 +1095,13 @@
         callbackTicket = mNextCallbackTicket++;
 
         VALIDATE_CONSISTENCY();
+
+        connectedApi = mCore->mConnectedApi;
+        lastQueuedFence = std::move(mLastQueueBufferFence);
+
+        mLastQueueBufferFence = std::move(acquireFence);
+        mLastQueuedCrop = item.mCrop;
+        mLastQueuedTransform = item.mTransform;
     } // Autolock scope
 
     // It is okay not to clear the GraphicBuffer when the consumer is SurfaceFlinger because
@@ -1079,9 +1125,6 @@
     // Call back without the main BufferQueue lock held, but with the callback
     // lock held so we can ensure that callbacks occur in order
 
-    int connectedApi;
-    sp<Fence> lastQueuedFence;
-
     { // scope for the lock
         std::unique_lock<std::mutex> lock(mCallbackMutex);
         while (callbackTicket != mCurrentCallbackTicket) {
@@ -1094,13 +1137,6 @@
             frameReplacedListener->onFrameReplaced(item);
         }
 
-        connectedApi = mCore->mConnectedApi;
-        lastQueuedFence = std::move(mLastQueueBufferFence);
-
-        mLastQueueBufferFence = std::move(acquireFence);
-        mLastQueuedCrop = item.mCrop;
-        mLastQueuedTransform = item.mTransform;
-
         ++mCurrentCallbackTicket;
         mCallbackCondition.notify_all();
     }
@@ -1342,6 +1378,9 @@
     }
 
     mCore->mAllowAllocation = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    mCore->mAdditionalOptions.clear();
+#endif
     VALIDATE_CONSISTENCY();
     return status;
 }
@@ -1410,6 +1449,9 @@
                     mCore->mSidebandStream.clear();
                     mCore->mDequeueCondition.notify_all();
                     mCore->mAutoPrerotation = false;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                    mCore->mAdditionalOptions.clear();
+#endif
                     listener = mCore->mConsumerListener;
                 } else if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
                     BQ_LOGE("disconnect: not connected (req=%d)", api);
@@ -1462,6 +1504,10 @@
         PixelFormat allocFormat = PIXEL_FORMAT_UNKNOWN;
         uint64_t allocUsage = 0;
         std::string allocName;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<gui::AdditionalOptions> allocOptions;
+        uint32_t allocOptionsGenId = 0;
+#endif
         { // Autolock scope
             std::unique_lock<std::mutex> lock(mCore->mMutex);
             mCore->waitWhileAllocatingLocked(lock);
@@ -1490,14 +1536,42 @@
             allocUsage = usage | mCore->mConsumerUsageBits;
             allocName.assign(mCore->mConsumerName.c_str(), mCore->mConsumerName.size());
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptions = mCore->mAdditionalOptions;
+            allocOptionsGenId = mCore->mAdditionalOptionsGenerationId;
+#endif
+
             mCore->mIsAllocating = true;
+
         } // Autolock scope
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<GraphicBufferAllocator::AdditionalOptions> tempOptions;
+        tempOptions.reserve(allocOptions.size());
+        for (const auto& it : allocOptions) {
+            tempOptions.emplace_back(it.name.c_str(), it.value);
+        }
+        const GraphicBufferAllocator::AllocationRequest allocRequest = {
+                .importBuffer = true,
+                .width = allocWidth,
+                .height = allocHeight,
+                .format = allocFormat,
+                .layerCount = BQ_LAYER_COUNT,
+                .usage = allocUsage,
+                .requestorName = allocName,
+                .extras = std::move(tempOptions),
+        };
+#endif
+
         Vector<sp<GraphicBuffer>> buffers;
         for (size_t i = 0; i < newBufferCount; ++i) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+#else
             sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
                     allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
                     allocUsage, allocName);
+#endif
 
             status_t result = graphicBuffer->initCheck();
 
@@ -1524,8 +1598,12 @@
             PixelFormat checkFormat = format != 0 ?
                     format : mCore->mDefaultBufferFormat;
             uint64_t checkUsage = usage | mCore->mConsumerUsageBits;
+            bool allocOptionsChanged = false;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptionsChanged = allocOptionsGenId != mCore->mAdditionalOptionsGenerationId;
+#endif
             if (checkWidth != allocWidth || checkHeight != allocHeight ||
-                checkFormat != allocFormat || checkUsage != allocUsage) {
+                checkFormat != allocFormat || checkUsage != allocUsage || allocOptionsChanged) {
                 // Something changed while we released the lock. Retry.
                 BQ_LOGV("allocateBuffers: size/format/usage changed while allocating. Retrying.");
                 mCore->mIsAllocating = false;
@@ -1543,6 +1621,9 @@
                 mCore->clearBufferSlotLocked(*slot); // Clean up the slot first
                 mSlots[*slot].mGraphicBuffer = buffers[i];
                 mSlots[*slot].mFence = Fence::NO_FENCE;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                mSlots[*slot].mAdditionalOptionsGenerationId = allocOptionsGenId;
+#endif
 
                 // freeBufferLocked puts this slot on the free slots list. Since
                 // we then attached a buffer, move the slot to free buffer list.
@@ -1653,9 +1734,10 @@
 status_t BufferQueueProducer::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer,
         sp<Fence>* outFence, float outTransformMatrix[16]) {
     ATRACE_CALL();
-    BQ_LOGV("getLastQueuedBuffer");
 
     std::lock_guard<std::mutex> lock(mCore->mMutex);
+    BQ_LOGV("getLastQueuedBuffer, slot=%d", mCore->mLastQueuedSlot);
+
     if (mCore->mLastQueuedSlot == BufferItem::INVALID_BUFFER_SLOT) {
         *outBuffer = nullptr;
         *outFence = Fence::NO_FENCE;
@@ -1679,10 +1761,11 @@
 status_t BufferQueueProducer::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence,
                                                   Rect* outRect, uint32_t* outTransform) {
     ATRACE_CALL();
-    BQ_LOGV("getLastQueuedBuffer");
 
     std::lock_guard<std::mutex> lock(mCore->mMutex);
-    if (mCore->mLastQueuedSlot == BufferItem::INVALID_BUFFER_SLOT) {
+    BQ_LOGV("getLastQueuedBuffer, slot=%d", mCore->mLastQueuedSlot);
+    if (mCore->mLastQueuedSlot == BufferItem::INVALID_BUFFER_SLOT ||
+        mSlots[mCore->mLastQueuedSlot].mBufferState.isDequeued()) {
         *outBuffer = nullptr;
         *outFence = Fence::NO_FENCE;
         return NO_ERROR;
@@ -1776,4 +1859,29 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t BufferQueueProducer::setAdditionalOptions(
+        const std::vector<gui::AdditionalOptions>& options) {
+    ATRACE_CALL();
+    BQ_LOGV("setAdditionalOptions, size = %zu", options.size());
+
+    if (!GraphicBufferAllocator::get().supportsAdditionalOptions()) {
+        return INVALID_OPERATION;
+    }
+
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+    if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
+        BQ_LOGE("setAdditionalOptions: BufferQueue not connected, cannot set additional options");
+        return NO_INIT;
+    }
+
+    if (mCore->mAdditionalOptions != options) {
+        mCore->mAdditionalOptions = options;
+        mCore->mAdditionalOptionsGenerationId++;
+    }
+    return NO_ERROR;
+}
+#endif
+
 } // namespace android
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 79c5fd1..0c8f3fa 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -143,9 +143,9 @@
 void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                              AChoreographer_frameCallback64 cb64,
                                              AChoreographer_vsyncCallback vsyncCallback, void* data,
-                                             nsecs_t delay) {
+                                             nsecs_t delay, CallbackType callbackType) {
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-    FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay};
+    FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay, callbackType};
     {
         std::lock_guard<std::mutex> _l{mLock};
         mFrameCallbacks.push(callback);
@@ -285,18 +285,8 @@
     }
 }
 
-void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t,
-                                  VsyncEventData vsyncEventData) {
-    std::vector<FrameCallback> callbacks{};
-    {
-        std::lock_guard<std::mutex> _l{mLock};
-        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-        while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
-            callbacks.push_back(mFrameCallbacks.top());
-            mFrameCallbacks.pop();
-        }
-    }
-    mLastVsyncEventData = vsyncEventData;
+void Choreographer::dispatchCallbacks(const std::vector<FrameCallback>& callbacks,
+                                      VsyncEventData vsyncEventData, nsecs_t timestamp) {
     for (const auto& cb : callbacks) {
         if (cb.vsyncCallback != nullptr) {
             ATRACE_FORMAT("AChoreographer_vsyncCallback %" PRId64,
@@ -319,6 +309,34 @@
     }
 }
 
+void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t,
+                                  VsyncEventData vsyncEventData) {
+    std::vector<FrameCallback> animationCallbacks{};
+    std::vector<FrameCallback> inputCallbacks{};
+    {
+        std::lock_guard<std::mutex> _l{mLock};
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+        while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
+            if (mFrameCallbacks.top().callbackType == CALLBACK_INPUT) {
+                inputCallbacks.push_back(mFrameCallbacks.top());
+            } else {
+                animationCallbacks.push_back(mFrameCallbacks.top());
+            }
+            mFrameCallbacks.pop();
+        }
+    }
+    mLastVsyncEventData = vsyncEventData;
+    // Callbacks with type CALLBACK_INPUT should always run first
+    {
+        ATRACE_FORMAT("CALLBACK_INPUT");
+        dispatchCallbacks(inputCallbacks, vsyncEventData, timestamp);
+    }
+    {
+        ATRACE_FORMAT("CALLBACK_ANIMATION");
+        dispatchCallbacks(animationCallbacks, vsyncEventData, timestamp);
+    }
+}
+
 void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) {
     ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this,
           to_string(displayId).c_str(), toString(connected));
@@ -344,6 +362,13 @@
     handleRefreshRateUpdates();
 }
 
+void Choreographer::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                              int32_t maxLevel) {
+    ALOGV("choreographer %p ~ received hdcp levels change event (displayId=%s, connectedLevel=%d, "
+          "maxLevel=%d), ignoring.",
+          this, to_string(displayId).c_str(), connectedLevel, maxLevel);
+}
+
 void Choreographer::handleMessage(const Message& message) {
     switch (message.what) {
         case MSG_SCHEDULE_CALLBACKS:
@@ -400,4 +425,8 @@
     return iter->second;
 }
 
+const sp<Looper> Choreographer::getLooper() {
+    return mLooper;
+}
+
 } // namespace android
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 5dd058c..f3de96d 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -195,6 +195,11 @@
                     dispatchFrameRateOverrides(ev.header.timestamp, ev.header.displayId,
                                                std::move(mFrameRateOverrides));
                     break;
+                case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+                    dispatchHdcpLevelsChanged(ev.header.displayId,
+                                              ev.hdcpLevelsChange.connectedLevel,
+                                              ev.hdcpLevelsChange.maxLevel);
+                    break;
                 default:
                     ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
                     break;
diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp
index bd640df..47cec07 100644
--- a/libs/gui/DisplayInfo.cpp
+++ b/libs/gui/DisplayInfo.cpp
@@ -37,8 +37,9 @@
         return BAD_VALUE;
     }
 
+    int32_t displayIdInt;
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
-    SAFE_PARCEL(parcel->readInt32, &displayId);
+    SAFE_PARCEL(parcel->readInt32, &displayIdInt);
     SAFE_PARCEL(parcel->readInt32, &logicalWidth);
     SAFE_PARCEL(parcel->readInt32, &logicalHeight);
     SAFE_PARCEL(parcel->readFloat, &dsdx);
@@ -48,6 +49,7 @@
     SAFE_PARCEL(parcel->readFloat, &dsdy);
     SAFE_PARCEL(parcel->readFloat, &ty);
 
+    displayId = ui::LogicalDisplayId{displayIdInt};
     transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
 
     return OK;
@@ -59,7 +61,7 @@
         return BAD_VALUE;
     }
 
-    SAFE_PARCEL(parcel->writeInt32, displayId);
+    SAFE_PARCEL(parcel->writeInt32, displayId.val());
     SAFE_PARCEL(parcel->writeInt32, logicalWidth);
     SAFE_PARCEL(parcel->writeInt32, logicalHeight);
     SAFE_PARCEL(parcel->writeFloat, transform.dsdx());
@@ -76,7 +78,7 @@
     using android::base::StringAppendF;
 
     out += prefix;
-    StringAppendF(&out, "DisplayViewport[id=%" PRId32 "]\n", displayId);
+    StringAppendF(&out, "DisplayViewport[id=%s]\n", displayId.toString().c_str());
     out += prefix;
     StringAppendF(&out, INDENT "Width=%" PRId32 ", Height=%" PRId32 "\n", logicalWidth,
                   logicalHeight);
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
index 11524e2..01aa7ed 100644
--- a/libs/gui/FrameRateUtils.cpp
+++ b/libs/gui/FrameRateUtils.cpp
@@ -42,6 +42,7 @@
 
     if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
         compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
+        compatibility != ANATIVEWINDOW_FRAME_RATE_GTE &&
         (!privileged ||
          (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
           compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index e81c098..0914480 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -80,6 +80,7 @@
     QUERY_MULTIPLE,
     GET_LAST_QUEUED_BUFFER2,
     SET_FRAME_RATE,
+    SET_ADDITIONAL_OPTIONS,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -778,6 +779,25 @@
         return result;
     }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    virtual status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        if (options.size() > 100) {
+            return BAD_VALUE;
+        }
+        data.writeInt32(options.size());
+        for (const auto& it : options) {
+            data.writeCString(it.name.c_str());
+            data.writeInt64(it.value);
+        }
+        status_t result = remote()->transact(SET_ADDITIONAL_OPTIONS, data, &reply);
+        if (result == NO_ERROR) {
+            result = reply.readInt32();
+        }
+        return result;
+    }
+#endif
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -981,6 +1001,13 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t IGraphicBufferProducer::setAdditionalOptions(const std::vector<gui::AdditionalOptions>&) {
+    // No-op for IGBP other than BufferQueue.
+    return INVALID_OPERATION;
+}
+#endif
+
 status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) {
     status_t res = OK;
     res = parcel->writeUint32(USE_BUFFER_QUEUE);
@@ -1533,6 +1560,28 @@
             return NO_ERROR;
         }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        case SET_ADDITIONAL_OPTIONS: {
+            CHECK_INTERFACE(IGraphicBuffer, data, reply);
+            int optionCount = data.readInt32();
+            if (optionCount < 0 || optionCount > 100) {
+                return BAD_VALUE;
+            }
+            std::vector<gui::AdditionalOptions> opts;
+            opts.reserve(optionCount);
+            for (int i = 0; i < optionCount; i++) {
+                const char* name = data.readCString();
+                int64_t value = 0;
+                if (name == nullptr || data.readInt64(&value) != NO_ERROR) {
+                    return BAD_VALUE;
+                }
+                opts.emplace_back(name, value);
+            }
+            status_t result = setAdditionalOptions(opts);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
+#endif
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp
deleted file mode 100644
index 15b2221..0000000
--- a/libs/gui/LayerDebugInfo.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gui/LayerDebugInfo.h>
-
-#include <android-base/stringprintf.h>
-
-#include <ui/DebugUtils.h>
-
-#include <binder/Parcel.h>
-
-using namespace android;
-using android::base::StringAppendF;
-
-#define RETURN_ON_ERROR(X) do {status_t res = (X); if (res != NO_ERROR) return res;} while(false)
-
-namespace android::gui {
-
-status_t LayerDebugInfo::writeToParcel(Parcel* parcel) const {
-    RETURN_ON_ERROR(parcel->writeCString(mName.c_str()));
-    RETURN_ON_ERROR(parcel->writeCString(mParentName.c_str()));
-    RETURN_ON_ERROR(parcel->writeCString(mType.c_str()));
-    RETURN_ON_ERROR(parcel->write(mTransparentRegion));
-    RETURN_ON_ERROR(parcel->write(mVisibleRegion));
-    RETURN_ON_ERROR(parcel->write(mSurfaceDamageRegion));
-    RETURN_ON_ERROR(parcel->writeUint32(mLayerStack));
-    RETURN_ON_ERROR(parcel->writeFloat(mX));
-    RETURN_ON_ERROR(parcel->writeFloat(mY));
-    RETURN_ON_ERROR(parcel->writeUint32(mZ));
-    RETURN_ON_ERROR(parcel->writeInt32(mWidth));
-    RETURN_ON_ERROR(parcel->writeInt32(mHeight));
-    RETURN_ON_ERROR(parcel->write(mCrop));
-    RETURN_ON_ERROR(parcel->writeFloat(mColor.r));
-    RETURN_ON_ERROR(parcel->writeFloat(mColor.g));
-    RETURN_ON_ERROR(parcel->writeFloat(mColor.b));
-    RETURN_ON_ERROR(parcel->writeFloat(mColor.a));
-    RETURN_ON_ERROR(parcel->writeUint32(mFlags));
-    RETURN_ON_ERROR(parcel->writeInt32(mPixelFormat));
-    RETURN_ON_ERROR(parcel->writeUint32(static_cast<uint32_t>(mDataSpace)));
-    for (size_t index = 0; index < 4; index++) {
-        RETURN_ON_ERROR(parcel->writeFloat(mMatrix[index / 2][index % 2]));
-    }
-    RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferWidth));
-    RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferHeight));
-    RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferStride));
-    RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferFormat));
-    RETURN_ON_ERROR(parcel->writeInt32(mNumQueuedFrames));
-    RETURN_ON_ERROR(parcel->writeBool(mIsOpaque));
-    RETURN_ON_ERROR(parcel->writeBool(mContentDirty));
-    RETURN_ON_ERROR(parcel->write(mStretchEffect));
-    return NO_ERROR;
-}
-
-status_t LayerDebugInfo::readFromParcel(const Parcel* parcel) {
-    mName = parcel->readCString();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mParentName = parcel->readCString();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mType = parcel->readCString();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    RETURN_ON_ERROR(parcel->read(mTransparentRegion));
-    RETURN_ON_ERROR(parcel->read(mVisibleRegion));
-    RETURN_ON_ERROR(parcel->read(mSurfaceDamageRegion));
-    RETURN_ON_ERROR(parcel->readUint32(&mLayerStack));
-    RETURN_ON_ERROR(parcel->readFloat(&mX));
-    RETURN_ON_ERROR(parcel->readFloat(&mY));
-    RETURN_ON_ERROR(parcel->readUint32(&mZ));
-    RETURN_ON_ERROR(parcel->readInt32(&mWidth));
-    RETURN_ON_ERROR(parcel->readInt32(&mHeight));
-    RETURN_ON_ERROR(parcel->read(mCrop));
-    mColor.r = parcel->readFloat();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mColor.g = parcel->readFloat();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mColor.b = parcel->readFloat();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mColor.a = parcel->readFloat();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    RETURN_ON_ERROR(parcel->readUint32(&mFlags));
-    RETURN_ON_ERROR(parcel->readInt32(&mPixelFormat));
-    // \todo [2017-07-25 kraita]: Static casting mDataSpace pointer to an uint32 does work. Better ways?
-    mDataSpace = static_cast<android_dataspace>(parcel->readUint32());
-    RETURN_ON_ERROR(parcel->errorCheck());
-    for (size_t index = 0; index < 4; index++) {
-        RETURN_ON_ERROR(parcel->readFloat(&mMatrix[index / 2][index % 2]));
-    }
-    RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferWidth));
-    RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferHeight));
-    RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferStride));
-    RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferFormat));
-    RETURN_ON_ERROR(parcel->readInt32(&mNumQueuedFrames));
-    RETURN_ON_ERROR(parcel->readBool(&mIsOpaque));
-    RETURN_ON_ERROR(parcel->readBool(&mContentDirty));
-    RETURN_ON_ERROR(parcel->read(mStretchEffect));
-    return NO_ERROR;
-}
-
-std::string to_string(const LayerDebugInfo& info) {
-    std::string result;
-
-    StringAppendF(&result, "+ %s (%s)\n", info.mType.c_str(), info.mName.c_str());
-    info.mTransparentRegion.dump(result, "TransparentRegion");
-    info.mVisibleRegion.dump(result, "VisibleRegion");
-    info.mSurfaceDamageRegion.dump(result, "SurfaceDamageRegion");
-    if (info.mStretchEffect.hasEffect()) {
-        const auto& se = info.mStretchEffect;
-        StringAppendF(&result,
-                      "  StretchEffect width = %f, height = %f vec=(%f, %f) "
-                      "maxAmount=(%f, %f)\n",
-                      se.width, se.height,
-                      se.vectorX, se.vectorY, se.maxAmountX, se.maxAmountY);
-    }
-
-    StringAppendF(&result, "      layerStack=%4d, z=%9d, pos=(%g,%g), size=(%4d,%4d), ",
-                  info.mLayerStack, info.mZ, static_cast<double>(info.mX),
-                  static_cast<double>(info.mY), info.mWidth, info.mHeight);
-
-    StringAppendF(&result, "crop=%s, ", to_string(info.mCrop).c_str());
-    StringAppendF(&result, "isOpaque=%1d, invalidate=%1d, ", info.mIsOpaque, info.mContentDirty);
-    StringAppendF(&result, "dataspace=%s, ", dataspaceDetails(info.mDataSpace).c_str());
-    StringAppendF(&result, "pixelformat=%s, ", decodePixelFormat(info.mPixelFormat).c_str());
-    StringAppendF(&result, "color=(%.3f,%.3f,%.3f,%.3f), flags=0x%08x, ",
-                  static_cast<double>(info.mColor.r), static_cast<double>(info.mColor.g),
-                  static_cast<double>(info.mColor.b), static_cast<double>(info.mColor.a),
-                  info.mFlags);
-    StringAppendF(&result, "tr=[%.2f, %.2f][%.2f, %.2f]", static_cast<double>(info.mMatrix[0][0]),
-                  static_cast<double>(info.mMatrix[0][1]), static_cast<double>(info.mMatrix[1][0]),
-                  static_cast<double>(info.mMatrix[1][1]));
-    result.append("\n");
-    StringAppendF(&result, "      parent=%s\n", info.mParentName.c_str());
-    StringAppendF(&result, "      activeBuffer=[%4ux%4u:%4u,%s],", info.mActiveBufferWidth,
-                  info.mActiveBufferHeight, info.mActiveBufferStride,
-                  decodePixelFormat(info.mActiveBufferFormat).c_str());
-    StringAppendF(&result, " queued-frames=%d", info.mNumQueuedFrames);
-    result.append("\n");
-    return result;
-}
-
-} // namespace android::gui
diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp
index 4e12fd3..535a021 100644
--- a/libs/gui/LayerMetadata.cpp
+++ b/libs/gui/LayerMetadata.cpp
@@ -100,27 +100,31 @@
 int32_t LayerMetadata::getInt32(uint32_t key, int32_t fallback) const {
     if (!has(key)) return fallback;
     const std::vector<uint8_t>& data = mMap.at(key);
-    if (data.size() < sizeof(uint32_t)) return fallback;
-    Parcel p;
-    p.setData(data.data(), data.size());
-    return p.readInt32();
+
+    // TODO: should handle when not equal?
+    if (data.size() < sizeof(int32_t)) return fallback;
+
+    int32_t result;
+    memcpy(&result, data.data(), sizeof(result));
+    return result;
 }
 
 void LayerMetadata::setInt32(uint32_t key, int32_t value) {
     std::vector<uint8_t>& data = mMap[key];
-    Parcel p;
-    p.writeInt32(value);
-    data.resize(p.dataSize());
-    memcpy(data.data(), p.data(), p.dataSize());
+    data.resize(sizeof(value));
+    memcpy(data.data(), &value, sizeof(value));
 }
 
 std::optional<int64_t> LayerMetadata::getInt64(uint32_t key) const {
     if (!has(key)) return std::nullopt;
     const std::vector<uint8_t>& data = mMap.at(key);
+
+    // TODO: should handle when not equal?
     if (data.size() < sizeof(int64_t)) return std::nullopt;
-    Parcel p;
-    p.setData(data.data(), data.size());
-    return p.readInt64();
+
+    int64_t result;
+    memcpy(&result, data.data(), sizeof(result));
+    return result;
 }
 
 std::string LayerMetadata::itemToString(uint32_t key, const char* separator) const {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 38fab9c..c82bde9 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -89,8 +89,7 @@
         frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         autoRefresh(false),
-        isTrustedOverlay(false),
-        borderEnabled(false),
+        trustedOverlay(gui::TrustedOverlay::UNSET),
         bufferCrop(Rect::INVALID_RECT),
         destinationFrame(Rect::INVALID_RECT),
         dropInputMode(gui::DropInputMode::NONE) {
@@ -122,12 +121,6 @@
     SAFE_PARCEL(output.write, transparentRegion);
     SAFE_PARCEL(output.writeUint32, bufferTransform);
     SAFE_PARCEL(output.writeBool, transformToDisplayInverse);
-    SAFE_PARCEL(output.writeBool, borderEnabled);
-    SAFE_PARCEL(output.writeFloat, borderWidth);
-    SAFE_PARCEL(output.writeFloat, borderColor.r);
-    SAFE_PARCEL(output.writeFloat, borderColor.g);
-    SAFE_PARCEL(output.writeFloat, borderColor.b);
-    SAFE_PARCEL(output.writeFloat, borderColor.a);
     SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dataspace));
     SAFE_PARCEL(output.write, hdrMetadata);
     SAFE_PARCEL(output.write, surfaceDamageRegion);
@@ -186,7 +179,7 @@
     SAFE_PARCEL(output.write, stretchEffect);
     SAFE_PARCEL(output.write, bufferCrop);
     SAFE_PARCEL(output.write, destinationFrame);
-    SAFE_PARCEL(output.writeBool, isTrustedOverlay);
+    SAFE_PARCEL(output.writeInt32, static_cast<uint32_t>(trustedOverlay));
 
     SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dropInputMode));
 
@@ -199,7 +192,7 @@
     SAFE_PARCEL(output.writeParcelable, trustedPresentationListener);
     SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio);
     SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio);
-    SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint))
+    SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint));
     return NO_ERROR;
 }
 
@@ -238,17 +231,6 @@
     SAFE_PARCEL(input.read, transparentRegion);
     SAFE_PARCEL(input.readUint32, &bufferTransform);
     SAFE_PARCEL(input.readBool, &transformToDisplayInverse);
-    SAFE_PARCEL(input.readBool, &borderEnabled);
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderWidth = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.r = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.g = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.b = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.a = tmpFloat;
 
     uint32_t tmpUint32 = 0;
     SAFE_PARCEL(input.readUint32, &tmpUint32);
@@ -326,7 +308,9 @@
     SAFE_PARCEL(input.read, stretchEffect);
     SAFE_PARCEL(input.read, bufferCrop);
     SAFE_PARCEL(input.read, destinationFrame);
-    SAFE_PARCEL(input.readBool, &isTrustedOverlay);
+    uint32_t trustedOverlayInt;
+    SAFE_PARCEL(input.readUint32, &trustedOverlayInt);
+    trustedOverlay = static_cast<gui::TrustedOverlay>(trustedOverlayInt);
 
     uint32_t mode;
     SAFE_PARCEL(input.readUint32, &mode);
@@ -484,6 +468,12 @@
             flags &= ~eLayerIsDisplayDecoration;
             ALOGE("Stripped attempt to set LayerIsDisplayDecoration in sanitize");
         }
+        if ((mask & eCanOccludePresentation) &&
+            !(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            flags &= ~eCanOccludePresentation;
+            mask &= ~eCanOccludePresentation;
+            ALOGE("Stripped attempt to set eCanOccludePresentation in sanitize");
+        }
     }
 
     if (what & layer_state_t::eInputInfoChanged) {
@@ -605,6 +595,10 @@
         desiredHdrSdrRatio = other.desiredHdrSdrRatio;
         currentHdrSdrRatio = other.currentHdrSdrRatio;
     }
+    if (other.what & eDesiredHdrHeadroomChanged) {
+        what |= eDesiredHdrHeadroomChanged;
+        desiredHdrSdrRatio = other.desiredHdrSdrRatio;
+    }
     if (other.what & eCachingHintChanged) {
         what |= eCachingHintChanged;
         cachingHint = other.cachingHint;
@@ -649,12 +643,6 @@
         what |= eShadowRadiusChanged;
         shadowRadius = other.shadowRadius;
     }
-    if (other.what & eRenderBorderChanged) {
-        what |= eRenderBorderChanged;
-        borderEnabled = other.borderEnabled;
-        borderWidth = other.borderWidth;
-        borderColor = other.borderColor;
-    }
     if (other.what & eDefaultFrameRateCompatibilityChanged) {
         what |= eDefaultFrameRateCompatibilityChanged;
         defaultFrameRateCompatibility = other.defaultFrameRateCompatibility;
@@ -688,7 +676,7 @@
     }
     if (other.what & eTrustedOverlayChanged) {
         what |= eTrustedOverlayChanged;
-        isTrustedOverlay = other.isTrustedOverlay;
+        trustedOverlay = other.trustedOverlay;
     }
     if (other.what & eStretchChanged) {
         what |= eStretchChanged;
@@ -768,6 +756,7 @@
     CHECK_DIFF(diff, eDataspaceChanged, other, dataspace);
     CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentHdrSdrRatio,
                 desiredHdrSdrRatio);
+    CHECK_DIFF(diff, eDesiredHdrHeadroomChanged, other, desiredHdrSdrRatio);
     CHECK_DIFF(diff, eCachingHintChanged, other, cachingHint);
     CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata);
     if (other.what & eSurfaceDamageRegionChanged &&
@@ -783,7 +772,6 @@
     CHECK_DIFF2(diff, eBackgroundColorChanged, other, bgColor, bgColorDataspace);
     if (other.what & eMetadataChanged) diff |= eMetadataChanged;
     CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius);
-    CHECK_DIFF3(diff, eRenderBorderChanged, other, borderEnabled, borderWidth, borderColor);
     CHECK_DIFF(diff, eDefaultFrameRateCompatibilityChanged, other, defaultFrameRateCompatibility);
     CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
     CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
@@ -793,7 +781,7 @@
     CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
     CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
     CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
-    CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
+    CHECK_DIFF(diff, eTrustedOverlayChanged, other, trustedOverlay);
     CHECK_DIFF(diff, eStretchChanged, other, stretchEffect);
     CHECK_DIFF(diff, eBufferCropChanged, other, bufferCrop);
     CHECK_DIFF(diff, eDestinationFrameChanged, other, destinationFrame);
diff --git a/libs/gui/LayerStatePermissions.cpp b/libs/gui/LayerStatePermissions.cpp
index 28697ca..c467cfd 100644
--- a/libs/gui/LayerStatePermissions.cpp
+++ b/libs/gui/LayerStatePermissions.cpp
@@ -23,31 +23,31 @@
 #include <gui/LayerState.h>
 
 namespace android {
-std::unordered_map<std::string, int> LayerStatePermissions::mPermissionMap = {
+std::vector<std::pair<String16, int>> LayerStatePermissions::mPermissionMap = {
         // If caller has ACCESS_SURFACE_FLINGER, they automatically get ROTATE_SURFACE_FLINGER
         // permission, as well
-        {"android.permission.ACCESS_SURFACE_FLINGER",
+        {String16("android.permission.ACCESS_SURFACE_FLINGER"),
          layer_state_t::Permission::ACCESS_SURFACE_FLINGER |
                  layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
-        {"android.permission.ROTATE_SURFACE_FLINGER",
+        {String16("android.permission.ROTATE_SURFACE_FLINGER"),
          layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
-        {"android.permission.INTERNAL_SYSTEM_WINDOW",
+        {String16("android.permission.INTERNAL_SYSTEM_WINDOW"),
          layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW},
 };
 
-static bool callingThreadHasPermission(const std::string& permission __attribute__((unused)),
+static bool callingThreadHasPermission(const String16& permission __attribute__((unused)),
                                        int pid __attribute__((unused)),
                                        int uid __attribute__((unused))) {
 #ifndef __ANDROID_VNDK__
     return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
-            PermissionCache::checkPermission(String16(permission.c_str()), pid, uid);
+            PermissionCache::checkPermission(permission, pid, uid);
 #endif // __ANDROID_VNDK__
     return false;
 }
 
 uint32_t LayerStatePermissions::getTransactionPermissions(int pid, int uid) {
     uint32_t permissions = 0;
-    for (auto [permissionName, permissionVal] : mPermissionMap) {
+    for (const auto& [permissionName, permissionVal] : mPermissionMap) {
         if (callingThreadHasPermission(permissionName, pid, uid)) {
             permissions |= permissionVal;
         }
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 07a0cfe..87fd448 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -342,12 +342,23 @@
 
     getFrameTimestamp(outRequestedPresentTime, events->requestedPresentTime);
     getFrameTimestamp(outLatchTime, events->latchTime);
-    getFrameTimestamp(outFirstRefreshStartTime, events->firstRefreshStartTime);
+
+    nsecs_t firstRefreshStartTime = NATIVE_WINDOW_TIMESTAMP_INVALID;
+    getFrameTimestamp(&firstRefreshStartTime, events->firstRefreshStartTime);
+    if (outFirstRefreshStartTime) {
+        *outFirstRefreshStartTime = firstRefreshStartTime;
+    }
+
     getFrameTimestamp(outLastRefreshStartTime, events->lastRefreshStartTime);
     getFrameTimestamp(outDequeueReadyTime, events->dequeueReadyTime);
 
-    getFrameTimestampFence(outAcquireTime, events->acquireFence,
+    nsecs_t acquireTime = NATIVE_WINDOW_TIMESTAMP_INVALID;
+    getFrameTimestampFence(&acquireTime, events->acquireFence,
             events->hasAcquireInfo());
+    if (outAcquireTime != nullptr) {
+        *outAcquireTime = acquireTime;
+    }
+
     getFrameTimestampFence(outGpuCompositionDoneTime,
             events->gpuCompositionDoneFence,
             events->hasGpuCompositionDoneInfo());
@@ -356,6 +367,16 @@
     getFrameTimestampFence(outReleaseTime, events->releaseFence,
             events->hasReleaseInfo());
 
+    // Fix up the GPU completion fence at this layer -- eglGetFrameTimestampsANDROID() expects
+    // that EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID > EGL_RENDERING_COMPLETE_TIME_ANDROID.
+    // This is typically true, but SurfaceFlinger may opt to cache prior GPU composition results,
+    // which breaks that assumption, so zero out GPU composition time.
+    if (outGpuCompositionDoneTime != nullptr
+            && *outGpuCompositionDoneTime > 0 && (acquireTime > 0 || firstRefreshStartTime > 0)
+            && *outGpuCompositionDoneTime <= std::max(acquireTime, firstRefreshStartTime)) {
+        *outGpuCompositionDoneTime = 0;
+    }
+
     return NO_ERROR;
 }
 
@@ -1454,6 +1475,9 @@
     case NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO:
         res = dispatchSetFrameTimelineInfo(args);
         break;
+    case NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS:
+        res = dispatchSetAdditionalOptions(args);
+        break;
     default:
         res = NAME_NOT_FOUND;
         break;
@@ -1812,6 +1836,24 @@
     return setFrameTimelineInfo(nativeWindowFtlInfo.frameNumber, ftlInfo);
 }
 
+int Surface::dispatchSetAdditionalOptions(va_list args) {
+    ATRACE_CALL();
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    const AHardwareBufferLongOptions* opts = va_arg(args, const AHardwareBufferLongOptions*);
+    const size_t optsSize = va_arg(args, size_t);
+    std::vector<gui::AdditionalOptions> convertedOpts;
+    convertedOpts.reserve(optsSize);
+    for (size_t i = 0; i < optsSize; i++) {
+        convertedOpts.emplace_back(opts[i].name, opts[i].value);
+    }
+    return setAdditionalOptions(convertedOpts);
+#else
+    (void)args;
+    return INVALID_OPERATION;
+#endif
+}
+
 bool Surface::transformToDisplayInverse() const {
     return (mTransform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) ==
             NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
@@ -2598,6 +2640,17 @@
     return BAD_VALUE;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t Surface::setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) {
+    if (!GraphicBufferAllocator::get().supportsAdditionalOptions()) {
+        return INVALID_OPERATION;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    return mGraphicBufferProducer->setAdditionalOptions(options);
+}
+#endif
+
 sp<IBinder> Surface::getSurfaceControlHandle() const {
     Mutex::Autolock lock(mMutex);
     return mSurfaceControlHandle;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 079ccda..b420aab 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -56,6 +56,7 @@
 
 #include <android-base/thread_annotations.h>
 #include <gui/LayerStatePermissions.h>
+#include <gui/ScreenCaptureResults.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 
@@ -88,6 +89,8 @@
 void emptyCallback(nsecs_t, const sp<Fence>&, const std::vector<SurfaceControlStats>&) {}
 } // namespace
 
+const std::string SurfaceComposerClient::kEmpty{};
+
 ComposerService::ComposerService()
 : Singleton<ComposerService>() {
     Mutex::Autolock _l(mLock);
@@ -705,6 +708,7 @@
 
 SurfaceComposerClient::Transaction::Transaction() {
     mId = generateId();
+    mTransactionCompletedListener = TransactionCompletedListener::getInstance();
 }
 
 SurfaceComposerClient::Transaction::Transaction(const Transaction& other)
@@ -722,6 +726,7 @@
     mComposerStates = other.mComposerStates;
     mInputWindowCommands = other.mInputWindowCommands;
     mListenerCallbacks = other.mListenerCallbacks;
+    mTransactionCompletedListener = TransactionCompletedListener::getInstance();
 }
 
 void SurfaceComposerClient::Transaction::sanitize(int pid, int uid) {
@@ -999,8 +1004,8 @@
 
         // register all surface controls for all callbackIds for this listener that is merging
         for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) {
-            TransactionCompletedListener::getInstance()
-                    ->addSurfaceControlToCallbacks(currentProcessCallbackInfo, surfaceControl);
+            mTransactionCompletedListener->addSurfaceControlToCallbacks(currentProcessCallbackInfo,
+                                                                        surfaceControl);
         }
     }
 
@@ -1275,19 +1280,22 @@
 }
 // ---------------------------------------------------------------------------
 
-sp<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure,
-                                                 float requestedRefereshRate) {
+sp<IBinder> SurfaceComposerClient::createVirtualDisplay(const std::string& displayName,
+                                                        bool isSecure, const std::string& uniqueId,
+                                                        float requestedRefreshRate) {
     sp<IBinder> display = nullptr;
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->createDisplay(std::string(
-                                                                             displayName.c_str()),
-                                                                     secure, requestedRefereshRate,
-                                                                     &display);
+            ComposerServiceAIDL::getComposerService()->createVirtualDisplay(displayName, isSecure,
+                                                                            uniqueId,
+                                                                            requestedRefreshRate,
+                                                                            &display);
     return status.isOk() ? display : nullptr;
 }
 
-void SurfaceComposerClient::destroyDisplay(const sp<IBinder>& display) {
-    ComposerServiceAIDL::getComposerService()->destroyDisplay(display);
+status_t SurfaceComposerClient::destroyVirtualDisplay(const sp<IBinder>& displayToken) {
+    return ComposerServiceAIDL::getComposerService()
+            ->destroyVirtualDisplay(displayToken)
+            .transactionError();
 }
 
 std::vector<PhysicalDisplayId> SurfaceComposerClient::getPhysicalDisplayIds() {
@@ -1353,7 +1361,7 @@
     auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()];
     callbackInfo.surfaceControls.insert(sc);
 
-    TransactionCompletedListener::getInstance()->addSurfaceControlToCallbacks(callbackInfo, sc);
+    mTransactionCompletedListener->addSurfaceControlToCallbacks(callbackInfo, sc);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition(
@@ -1671,7 +1679,7 @@
 
     std::shared_ptr<BufferData> bufferData = std::move(s->bufferData);
 
-    TransactionCompletedListener::getInstance()->removeReleaseBufferCallback(
+    mTransactionCompletedListener->removeReleaseBufferCallback(
             bufferData->generateReleaseCallbackId());
     s->what &= ~layer_state_t::eBufferChanged;
     s->bufferData = nullptr;
@@ -1714,8 +1722,7 @@
             bufferData->acquireFence = *fence;
             bufferData->flags |= BufferData::BufferDataChange::fenceChanged;
         }
-        bufferData->releaseBufferEndpoint =
-                IInterface::asBinder(TransactionCompletedListener::getIInstance());
+        bufferData->releaseBufferEndpoint = IInterface::asBinder(mTransactionCompletedListener);
         setReleaseBufferCallback(bufferData.get(), callback);
     }
 
@@ -1773,9 +1780,10 @@
         return;
     }
 
-    bufferData->releaseBufferListener = TransactionCompletedListener::getIInstance();
-    auto listener = TransactionCompletedListener::getInstance();
-    listener->setReleaseBufferCallback(bufferData->generateReleaseCallbackId(), callback);
+    bufferData->releaseBufferListener =
+            static_cast<sp<ITransactionCompletedListener>>(mTransactionCompletedListener);
+    mTransactionCompletedListener->setReleaseBufferCallback(bufferData->generateReleaseCallbackId(),
+                                                            callback);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDataspace(
@@ -1807,6 +1815,20 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDesiredHdrHeadroom(
+        const sp<SurfaceControl>& sc, float desiredRatio) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eDesiredHdrHeadroomChanged;
+    s->desiredHdrSdrRatio = desiredRatio;
+
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachingHint(
         const sp<SurfaceControl>& sc, gui::CachingHint cachingHint) {
     layer_state_t* s = getLayerState(sc);
@@ -1917,18 +1939,15 @@
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::addTransactionCallback(
         TransactionCompletedCallbackTakesContext callback, void* callbackContext,
         CallbackId::Type callbackType) {
-    auto listener = TransactionCompletedListener::getInstance();
-
     auto callbackWithContext = std::bind(callback, callbackContext, std::placeholders::_1,
                                          std::placeholders::_2, std::placeholders::_3);
-    const auto& surfaceControls =
-            mListenerCallbacks[TransactionCompletedListener::getIInstance()].surfaceControls;
+    const auto& surfaceControls = mListenerCallbacks[mTransactionCompletedListener].surfaceControls;
 
     CallbackId callbackId =
-            listener->addCallbackFunction(callbackWithContext, surfaceControls, callbackType);
+            mTransactionCompletedListener->addCallbackFunction(callbackWithContext, surfaceControls,
+                                                               callbackType);
 
-    mListenerCallbacks[TransactionCompletedListener::getIInstance()].callbackIds.emplace(
-            callbackId);
+    mListenerCallbacks[mTransactionCompletedListener].callbackIds.emplace(callbackId);
     return *this;
 }
 
@@ -2160,6 +2179,13 @@
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrustedOverlay(
         const sp<SurfaceControl>& sc, bool isTrustedOverlay) {
+    return setTrustedOverlay(sc,
+                             isTrustedOverlay ? gui::TrustedOverlay::ENABLED
+                                              : gui::TrustedOverlay::UNSET);
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrustedOverlay(
+        const sp<SurfaceControl>& sc, gui::TrustedOverlay trustedOverlay) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -2167,7 +2193,7 @@
     }
 
     s->what |= layer_state_t::eTrustedOverlayChanged;
-    s->isTrustedOverlay = isTrustedOverlay;
+    s->trustedOverlay = trustedOverlay;
     return *this;
 }
 
@@ -2235,23 +2261,6 @@
     return *this;
 }
 
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBorder(
-        const sp<SurfaceControl>& sc, bool shouldEnable, float width, const half4& color) {
-    layer_state_t* s = getLayerState(sc);
-    if (!s) {
-        mStatus = BAD_INDEX;
-        return *this;
-    }
-
-    s->what |= layer_state_t::eRenderBorderChanged;
-    s->borderEnabled = shouldEnable;
-    s->borderWidth = width;
-    s->borderColor = color;
-
-    registerSurfaceControlForCallback(sc);
-    return *this;
-}
-
 // ---------------------------------------------------------------------------
 
 DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
@@ -2335,8 +2344,9 @@
         const sp<SurfaceControl>& sc, TrustedPresentationCallback cb,
         const TrustedPresentationThresholds& thresholds, void* context,
         sp<SurfaceComposerClient::PresentationCallbackRAII>& outCallbackRef) {
-    auto listener = TransactionCompletedListener::getInstance();
-    outCallbackRef = listener->addTrustedPresentationCallback(cb, sc->getLayerId(), context);
+    outCallbackRef =
+            mTransactionCompletedListener->addTrustedPresentationCallback(cb, sc->getLayerId(),
+                                                                          context);
 
     layer_state_t* s = getLayerState(sc);
     if (!s) {
@@ -2353,8 +2363,7 @@
 
 SurfaceComposerClient::Transaction&
 SurfaceComposerClient::Transaction::clearTrustedPresentationCallback(const sp<SurfaceControl>& sc) {
-    auto listener = TransactionCompletedListener::getInstance();
-    listener->clearTrustedPresentationCallback(sc->getLayerId());
+    mTransactionCompletedListener->clearTrustedPresentationCallback(sc->getLayerId());
 
     layer_state_t* s = getLayerState(sc);
     if (!s) {
@@ -3117,6 +3126,9 @@
                                         ComposerServiceAIDL::getComposerService());
 }
 
+void SurfaceComposerClient::notifyShutdown() {
+    ComposerServiceAIDL::getComposerService()->notifyShutdown();
+}
 // ----------------------------------------------------------------------------
 
 status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
@@ -3138,11 +3150,19 @@
 }
 
 status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs,
-                                         const sp<IScreenCaptureListener>& captureListener) {
+                                         const sp<IScreenCaptureListener>& captureListener,
+                                         bool sync) {
     sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
     if (s == nullptr) return NO_INIT;
 
-    binder::Status status = s->captureLayers(captureArgs, captureListener);
+    binder::Status status;
+    if (sync) {
+        gui::ScreenCaptureResults captureResults;
+        status = s->captureLayersSync(captureArgs, &captureResults);
+        captureListener->onScreenCaptureCompleted(captureResults);
+    } else {
+        status = s->captureLayers(captureArgs, captureListener);
+    }
     return statusTFromBinderStatus(status);
 }
 
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index ba1d196..82d2554 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -73,14 +73,6 @@
     touchableRegion.orSelf(region);
 }
 
-bool WindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
-    return touchableRegion.contains(x, y);
-}
-
-bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
-    return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom;
-}
-
 bool WindowInfo::supportsSplitTouch() const {
     return !inputConfig.test(InputConfig::PREVENT_SPLITTING);
 }
@@ -109,7 +101,8 @@
             info.inputConfig == inputConfig && info.displayId == displayId &&
             info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop &&
             info.applicationInfo == applicationInfo && info.layoutParamsType == layoutParamsType &&
-            info.layoutParamsFlags == layoutParamsFlags;
+            info.layoutParamsFlags == layoutParamsFlags &&
+            info.canOccludePresentation == canOccludePresentation;
 }
 
 status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
@@ -153,13 +146,14 @@
         parcel->writeInt32(ownerUid.val()) ?:
         parcel->writeUtf8AsUtf16(packageName) ?:
         parcel->writeInt32(inputConfig.get()) ?:
-        parcel->writeInt32(displayId) ?:
+        parcel->writeInt32(displayId.val()) ?:
         applicationInfo.writeToParcel(parcel) ?:
         parcel->write(touchableRegion) ?:
         parcel->writeBool(replaceTouchableRegionWithCrop) ?:
         parcel->writeStrongBinder(touchableRegionCropHandle.promote()) ?:
-        parcel->writeStrongBinder(windowToken);
-        parcel->writeStrongBinder(focusTransferTarget);
+        parcel->writeStrongBinder(windowToken) ?:
+        parcel->writeStrongBinder(focusTransferTarget) ?:
+        parcel->writeBool(canOccludePresentation);
     // clang-format on
     return status;
 }
@@ -181,7 +175,8 @@
     }
 
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
-    int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt;
+    int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt,
+            displayIdInt;
     sp<IBinder> touchableRegionCropHandleSp;
 
     // clang-format off
@@ -204,13 +199,14 @@
         parcel->readInt32(&ownerUidInt) ?:
         parcel->readUtf8FromUtf16(&packageName) ?:
         parcel->readInt32(&inputConfigInt) ?:
-        parcel->readInt32(&displayId) ?:
+        parcel->readInt32(&displayIdInt) ?:
         applicationInfo.readFromParcel(parcel) ?:
         parcel->read(touchableRegion) ?:
         parcel->readBool(&replaceTouchableRegionWithCrop) ?:
         parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?:
         parcel->readNullableStrongBinder(&windowToken) ?:
-        parcel->readNullableStrongBinder(&focusTransferTarget);
+        parcel->readNullableStrongBinder(&focusTransferTarget) ?:
+        parcel->readBool(&canOccludePresentation);
 
     // clang-format on
 
@@ -226,6 +222,7 @@
     ownerPid = Pid{ownerPidInt};
     ownerUid = Uid{static_cast<uid_t>(ownerUidInt)};
     touchableRegionCropHandle = touchableRegionCropHandleSp;
+    displayId = ui::LogicalDisplayId{displayIdInt};
 
     return OK;
 }
@@ -258,10 +255,7 @@
     mInfo = handle->mInfo;
 }
 
-std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) {
-    const WindowInfo& info = *window.getInfo();
-    std::string transform;
-    info.transform.dump(transform, "transform", "    ");
+std::ostream& operator<<(std::ostream& out, const WindowInfo& info) {
     out << "name=" << info.name << ", id=" << info.id << ", displayId=" << info.displayId
         << ", inputConfig=" << info.inputConfig.string() << ", alpha=" << info.alpha << ", frame=["
         << info.frame.left << "," << info.frame.top << "][" << info.frame.right << ","
@@ -272,8 +266,17 @@
         << ", ownerUid=" << info.ownerUid.toString() << ", dispatchingTimeout="
         << std::chrono::duration_cast<std::chrono::milliseconds>(info.dispatchingTimeout).count()
         << "ms, token=" << info.token.get()
-        << ", touchOcclusionMode=" << ftl::enum_string(info.touchOcclusionMode) << "\n"
-        << transform;
+        << ", touchOcclusionMode=" << ftl::enum_string(info.touchOcclusionMode);
+    if (info.canOccludePresentation) out << ", canOccludePresentation";
+    std::string transform;
+    info.transform.dump(transform, "transform", "    ");
+    out << "\n" << transform;
+    return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) {
+    const WindowInfo& info = *window.getInfo();
+    out << info;
     return out;
 }
 
diff --git a/libs/gui/aidl/Android.bp b/libs/gui/aidl/Android.bp
new file mode 100644
index 0000000..8ed08c2
--- /dev/null
+++ b/libs/gui/aidl/Android.bp
@@ -0,0 +1,85 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
+}
+
+filegroup {
+    name: "libgui_unstructured_aidl_files",
+    srcs: [
+        ":libgui_extra_unstructured_aidl_files",
+
+        "android/gui/BitTube.aidl",
+        "android/gui/CaptureArgs.aidl",
+        "android/gui/DisplayCaptureArgs.aidl",
+        "android/gui/LayerCaptureArgs.aidl",
+        "android/gui/LayerMetadata.aidl",
+        "android/gui/ParcelableVsyncEventData.aidl",
+        "android/gui/ScreenCaptureResults.aidl",
+    ],
+}
+
+aidl_library {
+    name: "libgui_unstructured_aidl",
+    hdrs: [":libgui_unstructured_aidl_files"],
+}
+
+filegroup {
+    name: "libgui_interface_aidl_files",
+    srcs: [
+        ":libgui_extra_aidl_files",
+        "**/*.aidl",
+    ],
+    exclude_srcs: [":libgui_unstructured_aidl_files"],
+}
+
+aidl_interface {
+    name: "android.gui",
+    unstable: true,
+    srcs: [
+        ":libgui_interface_aidl_files",
+    ],
+    include_dirs: [
+        "frameworks/native/libs/gui",
+        "frameworks/native/libs/gui/aidl",
+    ],
+    headers: [
+        "libgui_aidl_hdrs",
+        "libgui_extra_unstructured_aidl_hdrs",
+    ],
+    backend: {
+        rust: {
+            enabled: true,
+            additional_rustlibs: [
+                "libgui_aidl_types_rs",
+            ],
+        },
+        java: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+        ndk: {
+            enabled: false,
+        },
+    },
+}
diff --git a/libs/gui/aidl/android/gui/BitTube.aidl b/libs/gui/aidl/android/gui/BitTube.aidl
index 6b0595e..eb231c1 100644
--- a/libs/gui/aidl/android/gui/BitTube.aidl
+++ b/libs/gui/aidl/android/gui/BitTube.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable BitTube cpp_header "private/gui/BitTube.h";
+parcelable BitTube cpp_header "private/gui/BitTube.h" rust_type "gui_aidl_types_rs::BitTube";
diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
index 920d949..9f198ca 100644
--- a/libs/gui/aidl/android/gui/CaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
+parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h" rust_type "gui_aidl_types_rs::CaptureArgs";
diff --git a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
index 2caa2b9..fc97dbf 100644
--- a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
@@ -16,4 +16,5 @@
 
 package android.gui;
 
-parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
+parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h" rust_type "gui_aidl_types_rs::DisplayCaptureArgs";
+
diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
index af138c7..13962fe 100644
--- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
@@ -42,6 +42,17 @@
     }
 
     /**
+     * Refers to the time after which the idle screen's refresh rate is to be reduced
+     */
+    parcelable IdleScreenRefreshRateConfig {
+
+        /**
+         *  The timeout value in milli seconds
+         */
+        int timeoutMillis;
+    }
+
+    /**
      * Base mode ID. This is what system defaults to for all other settings, or
      * if the refresh rate range is not available.
      */
@@ -72,4 +83,13 @@
      * never smaller.
      */
     RefreshRateRanges appRequestRanges;
+
+    /**
+     * The config to represent the maximum time (in ms) for which the display can remain in an idle
+     * state before reducing the refresh rate to conserve power.
+     * Null value refers that the device is not configured to dynamically reduce the refresh rate
+     * based on external conditions.
+     * -1 refers to the current conditions requires no timeout
+     */
+    @nullable IdleScreenRefreshRateConfig idleScreenRefreshRateConfig;
 }
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index e3122bc..6d018ea 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -43,9 +43,9 @@
 import android.gui.IWindowInfosListener;
 import android.gui.IWindowInfosPublisher;
 import android.gui.LayerCaptureArgs;
-import android.gui.LayerDebugInfo;
 import android.gui.OverlayProperties;
 import android.gui.PullAtomData;
+import android.gui.ScreenCaptureResults;
 import android.gui.ARect;
 import android.gui.SchedulingPolicy;
 import android.gui.StalledTransactionInfo;
@@ -72,7 +72,7 @@
     void bootFinished();
 
     /**
-     * Create a display event connection
+     * Create a display event connection.
      *
      * layerHandle
      *     Optional binder handle representing a Layer in SF to associate the new
@@ -89,12 +89,14 @@
     @nullable ISurfaceComposerClient createConnection();
 
     /**
-     * Create a virtual display
+     * Create a virtual display.
      *
      * displayName
-     *     The name of the virtual display
-     * secure
-     *     Whether this virtual display is secure
+     *     The name of the virtual display.
+     * isSecure
+     *     Whether this virtual display is secure.
+     * uniqueId
+     *     The unique ID for the display.
      * requestedRefreshRate
      *     The refresh rate, frames per second, to request on the virtual display.
      *     This is just a request, the actual rate may be adjusted to align well
@@ -103,14 +105,14 @@
      *
      * requires ACCESS_SURFACE_FLINGER permission.
      */
-    @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure,
-            float requestedRefreshRate);
+    @nullable IBinder createVirtualDisplay(@utf8InCpp String displayName, boolean isSecure,
+            @utf8InCpp String uniqueId, float requestedRefreshRate);
 
     /**
-     * Destroy a virtual display
+     * Destroy a virtual display.
      * requires ACCESS_SURFACE_FLINGER permission.
      */
-    void destroyDisplay(IBinder display);
+    void destroyVirtualDisplay(IBinder displayToken);
 
     /**
      * Get stable IDs for connected physical displays.
@@ -245,6 +247,16 @@
     /**
      * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
      * This requires READ_FRAME_BUFFER permission. This function will fail if there
+     * is a secure window on screen. This is a blocking call and will return the
+     * ScreenCaptureResults, including the captured buffer. Because this is blocking, the
+     * caller doesn't care about the fence and the binder thread in SurfaceFlinger will wait
+     * on the fence to fire before returning the results.
+     */
+    ScreenCaptureResults captureLayersSync(in LayerCaptureArgs args);
+
+    /**
+     * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
+     * This requires READ_FRAME_BUFFER permission. This function will fail if there
      * is a secure window on screen
      */
     oneway void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
@@ -278,13 +290,6 @@
     PullAtomData onPullAtom(int atomId);
 
     /**
-     * Gets the list of active layers in Z order for debugging purposes
-     *
-     * Requires the ACCESS_SURFACE_FLINGER permission.
-     */
-    List<LayerDebugInfo> getLayerDebugInfo();
-
-    /**
      * Gets the composition preference of the default data space and default pixel format,
      * as well as the wide color gamut data space and wide color gamut pixel format.
      * If the wide color gamut data space is V0_SRGB, then it implies that the platform
@@ -568,4 +573,11 @@
     @nullable StalledTransactionInfo getStalledTransactionInfo(int pid);
 
     SchedulingPolicy getSchedulingPolicy();
+
+    /**
+     * Notifies the SurfaceFlinger that the ShutdownThread is running. When it is called,
+     * transaction traces will be captured and writted into a file.
+     * This method should not block the ShutdownThread therefore it's handled asynchronously.
+     */
+    oneway void notifyShutdown();
 }
diff --git a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
index f0def50..18d293f 100644
--- a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h";
+parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h" rust_type "gui_aidl_types_rs::LayerCaptureArgs";
diff --git a/libs/gui/aidl/android/gui/LayerMetadata.aidl b/libs/gui/aidl/android/gui/LayerMetadata.aidl
index 1368ac5..d8121be 100644
--- a/libs/gui/aidl/android/gui/LayerMetadata.aidl
+++ b/libs/gui/aidl/android/gui/LayerMetadata.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable LayerMetadata cpp_header "gui/LayerMetadata.h";
+parcelable LayerMetadata cpp_header "gui/LayerMetadata.h" rust_type "gui_aidl_types_rs::LayerMetadata";
diff --git a/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
index ba76671..53f443a 100644
--- a/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
+++ b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h";
+parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h" rust_type "gui_aidl_types_rs::VsyncEventData";
diff --git a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
index 9908edd..97a9035 100644
--- a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
+++ b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h";
\ No newline at end of file
+parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h" rust_type "gui_aidl_types_rs::ScreenCaptureResults";
\ No newline at end of file
diff --git a/libs/gui/android/gui/DisplayInfo.aidl b/libs/gui/android/gui/DisplayInfo.aidl
index 30c0885..3b16724 100644
--- a/libs/gui/android/gui/DisplayInfo.aidl
+++ b/libs/gui/android/gui/DisplayInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable DisplayInfo cpp_header "gui/DisplayInfo.h";
+parcelable DisplayInfo cpp_header "gui/DisplayInfo.h" rust_type "gui_aidl_types_rs::DisplayInfo";
diff --git a/libs/gui/android/gui/TrustedOverlay.aidl b/libs/gui/android/gui/TrustedOverlay.aidl
new file mode 100644
index 0000000..06fb5f0
--- /dev/null
+++ b/libs/gui/android/gui/TrustedOverlay.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2024, 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 android.gui;
+
+
+/**
+  * Trusted overlay state prevents layers from being considered as obscuring for
+  * input occlusion detection purposes.
+  *
+  * @hide
+  */
+@Backing(type="int")
+enum TrustedOverlay {
+    /**
+      * The default, layer will inherit the state from its parents. If the parent state is also
+      * unset, the layer will be considered as untrusted.
+      */
+    UNSET,
+
+    /**
+      * Treats this layer and all its children as an untrusted overlay. This will override any
+      * state set by its parent layers.
+      */
+    DISABLED,
+
+    /**
+      * Treats this layer and all its children as a trusted overlay unless the child layer
+      * explicitly disables its trusted state.
+      */
+    ENABLED
+}
diff --git a/libs/gui/android/gui/WindowInfo.aidl b/libs/gui/android/gui/WindowInfo.aidl
index 2c85d15..b9d5ccf 100644
--- a/libs/gui/android/gui/WindowInfo.aidl
+++ b/libs/gui/android/gui/WindowInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable WindowInfo cpp_header "gui/WindowInfo.h";
+parcelable WindowInfo cpp_header "gui/WindowInfo.h" rust_type "gui_aidl_types_rs::WindowInfo";
diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl
index 0c6109d..5c23e08 100644
--- a/libs/gui/android/gui/WindowInfosUpdate.aidl
+++ b/libs/gui/android/gui/WindowInfosUpdate.aidl
@@ -19,4 +19,4 @@
 import android.gui.DisplayInfo;
 import android.gui.WindowInfo;
 
-parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h";
+parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h" rust_type "gui_aidl_types_rs::WindowInfosUpdate";
diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp
deleted file mode 100644
index cd738ac..0000000
--- a/libs/gui/fuzzer/Android.bp
+++ /dev/null
@@ -1,146 +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.
- */
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_defaults {
-    name: "libgui_fuzzer_defaults",
-    defaults: ["android.hardware.power-ndk_shared"],
-    static_libs: [
-        "android.hidl.token@1.0-utils",
-        "libbinder_random_parcel",
-        "libgui_aidl_static",
-        "libgui_window_info_static",
-        "libpdx",
-        "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: [
-        "android.hardware.configstore@1.0",
-        "android.hardware.configstore-utils",
-        "android.hardware.graphics.bufferqueue@1.0",
-        "android.hardware.graphics.bufferqueue@2.0",
-        "android.hidl.token@1.0",
-        "libSurfaceFlingerProp",
-        "libgui",
-        "libbase",
-        "liblog",
-        "libEGL",
-        "libGLESv2",
-        "libbinder",
-        "libcutils",
-        "libhidlbase",
-        "libinput",
-        "libui",
-        "libutils",
-        "libnativewindow",
-        "libvndksupport",
-    ],
-    header_libs: [
-        "libdvr_headers",
-        "libui_fuzzableDataspaces_headers",
-    ],
-    fuzz_config: {
-        cc: [
-            "android-media-fuzzing-reports@google.com",
-        ],
-        componentid: 155276,
-        hotlists: [
-            "4593311",
-        ],
-        description: "The fuzzer targets the APIs of libgui library",
-        vector: "local_no_privileges_required",
-        service_privilege: "privileged",
-        users: "multi_user",
-        fuzzed_code_usage: "shipped",
-    },
-}
-
-cc_fuzz {
-    name: "libgui_surfaceComposer_fuzzer",
-    srcs: [
-        "libgui_surfaceComposer_fuzzer.cpp",
-    ],
-    defaults: [
-        "libgui_fuzzer_defaults",
-        "service_fuzzer_defaults",
-    ],
-}
-
-cc_fuzz {
-    name: "libgui_surfaceComposerClient_fuzzer",
-    srcs: [
-        "libgui_surfaceComposerClient_fuzzer.cpp",
-    ],
-    defaults: [
-        "libgui_fuzzer_defaults",
-        "service_fuzzer_defaults",
-    ],
-}
-
-cc_fuzz {
-    name: "libgui_parcelable_fuzzer",
-    srcs: [
-        "libgui_parcelable_fuzzer.cpp",
-    ],
-    defaults: [
-        "libgui_fuzzer_defaults",
-    ],
-}
-
-cc_fuzz {
-    name: "libgui_bufferQueue_fuzzer",
-    srcs: [
-        "libgui_bufferQueue_fuzzer.cpp",
-    ],
-    defaults: [
-        "libgui_fuzzer_defaults",
-    ],
-}
-
-cc_fuzz {
-    name: "libgui_consumer_fuzzer",
-    srcs: [
-        "libgui_consumer_fuzzer.cpp",
-    ],
-    defaults: [
-        "libgui_fuzzer_defaults",
-    ],
-}
-
-cc_fuzz {
-    name: "libgui_displayEvent_fuzzer",
-    srcs: [
-        "libgui_displayEvent_fuzzer.cpp",
-    ],
-    defaults: [
-        "libgui_fuzzer_defaults",
-    ],
-}
diff --git a/libs/gui/fuzzer/README.md b/libs/gui/fuzzer/README.md
deleted file mode 100644
index 96e27c9..0000000
--- a/libs/gui/fuzzer/README.md
+++ /dev/null
@@ -1,219 +0,0 @@
-# Fuzzers for Libgui
-
-## Table of contents
-+ [libgui_surfaceComposer_fuzzer](#SurfaceComposer)
-+ [libgui_surfaceComposerClient_fuzzer](#SurfaceComposerClient)
-+ [libgui_parcelable_fuzzer](#Libgui_Parcelable)
-+ [libgui_bufferQueue_fuzzer](#BufferQueue)
-+ [libgui_consumer_fuzzer](#Libgui_Consumer)
-+ [libgui_displayEvent_fuzzer](#LibGui_DisplayEvent)
-
-# <a name="libgui_surfaceComposer_fuzzer"></a> Fuzzer for SurfaceComposer
-
-SurfaceComposer supports the following parameters:
-1. SurfaceWidth (parameter name:`width`)
-2. SurfaceHeight (parameter name:`height`)
-3. TransactionStateFlags (parameter name:`flags`)
-4. TransformHint (parameter name:`outTransformHint`)
-5. SurfacePixelFormat (parameter name:`format`)
-6. LayerId (parameter name:`outLayerId`)
-7. SurfaceComposerTags (parameter name:`surfaceTag`)
-8. PowerBoostID (parameter name:`boostId`)
-9. VsyncSource (parameter name:`vsyncSource`)
-10. EventRegistrationFlags (parameter name:`eventRegistration`)
-11. FrameRateCompatibility (parameter name:`frameRateCompatibility`)
-12. ChangeFrameRateStrategy (parameter name:`changeFrameRateStrategy`)
-13. HdrTypes (parameter name:`hdrTypes`)
-
-| Parameter| Valid Values| Configured Value|
-|------------- |-------------| ----- |
-|`surfaceTag` | 0.`BnSurfaceComposer::BOOT_FINISHED`, 1.`BnSurfaceComposer::CREATE_CONNECTION`, 2.`BnSurfaceComposer::GET_STATIC_DISPLAY_INFO`, 3.`BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION`, 4.`BnSurfaceComposer::CREATE_DISPLAY`, 5.`BnSurfaceComposer::DESTROY_DISPLAY`, 6.`BnSurfaceComposer::GET_PHYSICAL_DISPLAY_TOKEN`, 7.`BnSurfaceComposer::SET_TRANSACTION_STATE`, 8.`BnSurfaceComposer::AUTHENTICATE_SURFACE`, 9.`BnSurfaceComposer::GET_SUPPORTED_FRAME_TIMESTAMPS`, 10.`BnSurfaceComposer::GET_DISPLAY_STATE`, 11.`BnSurfaceComposer::CAPTURE_DISPLAY`, 12.`BnSurfaceComposer::CAPTURE_LAYERS`, 13.`BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS`, 14.`BnSurfaceComposer::GET_ANIMATION_FRAME_STATS`, 15.`BnSurfaceComposer::SET_POWER_MODE`, 16.`BnSurfaceComposer::GET_DISPLAY_STATS`, 17.`BnSurfaceComposer::SET_ACTIVE_COLOR_MODE`, 18.`BnSurfaceComposer::ENABLE_VSYNC_INJECTIONS`, 19.`BnSurfaceComposer::INJECT_VSYNC`, 20.`BnSurfaceComposer::GET_LAYER_DEBUG_INFO`, 21.`BnSurfaceComposer::GET_COMPOSITION_PREFERENCE`, 22.`BnSurfaceComposer::GET_COLOR_MANAGEMENT`, 23.`BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES`, 24.`BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED`, 25.`BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLE`, 26.`BnSurfaceComposer::GET_PROTECTED_CONTENT_SUPPORT`, 27.`BnSurfaceComposer::IS_WIDE_COLOR_DISPLAY`, 28.`BnSurfaceComposer::GET_DISPLAY_NATIVE_PRIMARIES`, 29.`BnSurfaceComposer::GET_PHYSICAL_DISPLAY_IDS`, 30.`BnSurfaceComposer::ADD_REGION_SAMPLING_LISTENER`, 31.`BnSurfaceComposer::REMOVE_REGION_SAMPLING_LISTENER`, 32.`BnSurfaceComposer::SET_DESIRED_DISPLAY_MODE_SPECS`, 33.`BnSurfaceComposer::GET_DESIRED_DISPLAY_MODE_SPECS`, 34.`BnSurfaceComposer::GET_DISPLAY_BRIGHTNESS_SUPPORT`, 35.`BnSurfaceComposer::SET_DISPLAY_BRIGHTNESS`, 36.`BnSurfaceComposer::CAPTURE_DISPLAY_BY_ID`, 37.`BnSurfaceComposer::NOTIFY_POWER_BOOST`, 38.`BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS`, 39.`BnSurfaceComposer::SET_AUTO_LOW_LATENCY_MODE`, 40.`BnSurfaceComposer::SET_GAME_CONTENT_TYPE`, 41.`BnSurfaceComposer::SET_FRAME_RATE`, 42.`BnSurfaceComposer::ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN`, 43.`BnSurfaceComposer::SET_FRAME_TIMELINE_INFO`, 44.`BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER`, 45.`BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY`, 46.`BnSurfaceComposer::GET_MAX_ACQUIRED_BUFFER_COUNT`, 47.`BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO`, 48.`BnSurfaceComposer::ADD_FPS_LISTENER`, 49.`BnSurfaceComposer::REMOVE_FPS_LISTENER`, 50.`BnSurfaceComposer::OVERRIDE_HDR_TYPES`, 51.`BnSurfaceComposer::ADD_HDR_LAYER_INFO_LISTENER`, 52.`BnSurfaceComposer::REMOVE_HDR_LAYER_INFO_LISTENER`, 53.`BnSurfaceComposer::ON_PULL_ATOM`, 54.`BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER`, 55.`BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER` | Value obtained from FuzzedDataProvider|
-|`boostId`| 0.`hardware::power::Boost::INTERACTION`, 1.`hardware::power::Boost::DISPLAY_UPDATE_IMMINENT`, 2.`hardware::power::Boost::ML_ACC`, 3.`hardware::power::Boost::AUDIO_LAUNCH`, 4.`hardware::power::Boost::CAMERA_LAUNCH`, 5.`hardware::power::Boost::CAMERA_SHOT` |Value obtained from FuzzedDataProvider|
-|`vsyncSource`| 0.`ISurfaceComposer::eVsyncSourceApp`, 1.`ISurfaceComposer::eVsyncSourceSurfaceFlinger`, |Value obtained from FuzzedDataProvider|
-|`eventRegistration`| 0.`ISurfaceComposer::EventRegistration::modeChanged`, 1.`ISurfaceComposer::EventRegistration::frameRateOverride` |Value obtained from FuzzedDataProvider|
-|`frameRateCompatibility`| 0.`ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT`, 1.`ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE` |Value obtained from FuzzedDataProvider|
-|`changeFrameRateStrategy`| 0.`ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS`, 1.`ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS` |Value obtained from FuzzedDataProvider|
-|`hdrTypes`| 0.`ui::Hdr::DOLBY_VISION`, 1.`ui::Hdr::HDR10`, 2.`ui::Hdr::HLG`, 3.`ui::Hdr::HDR10_PLUS` |Value obtained from FuzzedDataProvider|
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) libgui_surfaceComposer_fuzzer
-```
-2. Run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/libgui_surfaceComposer_fuzzer/libgui_surfaceComposer_fuzzer
-```
-
-# <a name="libgui_surfaceComposerClient_fuzzer"></a> Fuzzer for SurfaceComposerClient
-
-SurfaceComposerClient supports the following data sources:
-1. SurfaceWidth (parameter name:`width`)
-2. SurfaceHeight (parameter name:`height`)
-3. TransactionStateFlags (parameter name:`flags`)
-4. TransformHint (parameter name:`outTransformHint`)
-5. SurfacePixelFormat (parameter name:`format`)
-6. LayerId (parameter name:`outLayerId`)
-7. SurfaceComposerClientTags (parameter name:`surfaceTag`)
-8. DefaultMode (parameter name:`defaultMode`)
-9. PrimaryRefreshRateMin (parameter name:`primaryRefreshRateMin`)
-10. PrimaryRefreshRateMax (parameter name:`primaryRefreshRateMax`)
-11. AppRefreshRateMin (parameter name:`appRefreshRateMin`)
-12. AppRefreshRateMax (parameter name:`appRefreshRateMax`)
-13. DisplayPowerMode (parameter name:`mode`)
-14. CacheId (parameter name:`cacheId`)
-15. DisplayBrightness (parameter name:`brightness`)
-16. PowerBoostID (parameter name:`boostId`)
-17. AtomId (parameter name:`atomId`)
-18. ComponentMask (parameter name:`componentMask`)
-19. MaxFrames (parameter name:`maxFrames`)
-20. TaskId (parameter name:`taskId`)
-21. Alpha (parameter name:`aplha`)
-22. CornerRadius (parameter name:`cornerRadius`)
-23. BackgroundBlurRadius (parameter name:`backgroundBlurRadius`)
-24. Half3Color (parameter name:`color`)
-25. LayerStack (parameter name:`layerStack`)
-26. Dataspace (parameter name:`dataspace`)
-27. Api (parameter name:`api`)
-28. Priority (parameter name:`priority`)
-29. TouchableRegionPointX (parameter name:`pointX`)
-30. TouchableRegionPointY (parameter name:`pointY`)
-31. ColorMode (parameter name:`colorMode`)
-32. WindowInfoFlags (parameter name:`flags`)
-33. WindowInfoTransformOrientation (parameter name:`transform`)
-
-| Parameter| Valid Values| Configured Value|
-|------------- |-------------| ----- |
-|`surfaceTag`| 0.`Tag::CREATE_SURFACE`, 1.`Tag::CREATE_WITH_SURFACE_PARENT`, 2.`Tag::CLEAR_LAYER_FRAME_STATS`, 3.`Tag::GET_LAYER_FRAME_STATS`, 4.`Tag::MIRROR_SURFACE`, 5.`Tag::LAST` |Value obtained from FuzzedDataProvider|
-|`mode`| 0.`gui::TouchOcclusionMode::BLOCK_UNTRUSTED`, 1.`gui::TouchOcclusionMode::USE_OPACITY`, 2.`gui::TouchOcclusionMode::ALLOW` |Value obtained from FuzzedDataProvider|
-|`boostId`| 0.`hardware::power::Boost::INTERACTION`, 1.`hardware::power::Boost::DISPLAY_UPDATE_IMMINENT`, 2.`hardware::power::Boost::ML_ACC`, 3.`hardware::power::Boost::AUDIO_LAUNCH`, 4.`hardware::power::Boost::CAMERA_LAUNCH`, 5.`hardware::power::Boost::CAMERA_SHOT` |Value obtained from FuzzedDataProvider|
-|`colorMode`|0.`ui::ColorMode::NATIVE`, 1.`ui::ColorMode::STANDARD_BT601_625`, 2.`ui::ColorMode::STANDARD_BT601_625_UNADJUSTED`, 3.`ui::ColorMode::STANDARD_BT601_525`, 4.`ui::ColorMode::STANDARD_BT601_525_UNADJUSTED`, 5.`ui::ColorMode::STANDARD_BT709`, 6.`ui::ColorMode::DCI_P3`, 7.`ui::ColorMode::SRGB`, 8.`ui::ColorMode::ADOBE_RGB`, 9.`ui::ColorMode::DISPLAY_P3`, 10.`ui::ColorMode::BT2020`, 11.`ui::ColorMode::BT2100_PQ`, 12.`ui::ColorMode::BT2100_HLG`, 13.`ui::ColorMode::DISPLAY_BT2020` |Value obtained from FuzzedDataProvider|
-|`flags`|0 .`gui::WindowInfo::Flag::ALLOW_LOCK_WHILE_SCREEN_ON`, 1.`gui::WindowInfo::Flag::DIM_BEHIND`, 2.`gui::WindowInfo::Flag::BLUR_BEHIND`, 3.`gui::WindowInfo::Flag::NOT_FOCUSABLE`, 4.`gui::WindowInfo::Flag::NOT_TOUCHABLE`, 5.`gui::WindowInfo::Flag::NOT_TOUCH_MODAL`, 6.`gui::WindowInfo::Flag::TOUCHABLE_WHEN_WAKING`, 7.`gui::WindowInfo::Flag::KEEP_SCREEN_ON`, 8.`gui::WindowInfo::Flag::LAYOUT_IN_SCREEN`, 9.`gui::WindowInfo::Flag::LAYOUT_NO_LIMITS`, 10.`gui::WindowInfo::Flag::FULLSCREEN`, 11.`gui::WindowInfo::Flag::FORCE_NOT_FULLSCREEN`, 12.`gui::WindowInfo::Flag::DITHER`, 13.`gui::WindowInfo::Flag::SECURE`, 14.`gui::WindowInfo::Flag::SCALED`, 15.`gui::WindowInfo::Flag::IGNORE_CHEEK_PRESSES`, 16.`gui::WindowInfo::Flag::LAYOUT_INSET_DECOR`, 17.`gui::WindowInfo::Flag::ALT_FOCUSABLE_IM`, 18.`gui::WindowInfo::Flag::WATCH_OUTSIDE_TOUCH`, 19.`gui::WindowInfo::Flag::SHOW_WHEN_LOCKED`, 20.`gui::WindowInfo::Flag::SHOW_WALLPAPER`, 21.`gui::WindowInfo::Flag::TURN_SCREEN_ON`, 22.`gui::WindowInfo::Flag::DISMISS_KEYGUARD`, 23.`gui::WindowInfo::Flag::SPLIT_TOUCH`, 24.`gui::WindowInfo::Flag::HARDWARE_ACCELERATED`, 25.`gui::WindowInfo::Flag::LAYOUT_IN_OVERSCAN`, 26.`gui::WindowInfo::Flag::TRANSLUCENT_STATUS`, 27.`gui::WindowInfo::Flag::TRANSLUCENT_NAVIGATION`, 28.`gui::WindowInfo::Flag::LOCAL_FOCUS_MODE`, 29.`gui::WindowInfo::Flag::SLIPPERY`, 30.`gui::WindowInfo::Flag::LAYOUT_ATTACHED_IN_DECOR`, 31.`gui::WindowInfo::Flag::DRAWS_SYSTEM_BAR_BACKGROUNDS`, |Value obtained from FuzzedDataProvider|
-|`dataspace`| 0.`ui::Dataspace::UNKNOWN`, 1.`ui::Dataspace::ARBITRARY`, 2.`ui::Dataspace::STANDARD_SHIFT`, 3.`ui::Dataspace::STANDARD_MASK`, 4.`ui::Dataspace::STANDARD_UNSPECIFIED`, 5.`ui::Dataspace::STANDARD_BT709`, 6.`ui::Dataspace::STANDARD_BT601_625`, 7.`ui::Dataspace::STANDARD_BT601_625_UNADJUSTED`, 8.`ui::Dataspace::STANDARD_BT601_525`, 9.`ui::Dataspace::STANDARD_BT601_525_UNADJUSTED`, 10.`ui::Dataspace::STANDARD_BT2020`, 11.`ui::Dataspace::STANDARD_BT2020_CONSTANT_LUMINANCE`, 12.`ui::Dataspace::STANDARD_BT470M`, 13.`ui::Dataspace::STANDARD_FILM`, 14.`ui::Dataspace::STANDARD_DCI_P3`, 15.`ui::Dataspace::STANDARD_ADOBE_RGB`, 16.`ui::Dataspace::TRANSFER_SHIFT`, 17.`ui::Dataspace::TRANSFER_MASK`, 18.`ui::Dataspace::TRANSFER_UNSPECIFIED`, 19.`ui::Dataspace::TRANSFER_LINEAR`, 20.`ui::Dataspace::TRANSFER_SRGB`, 21.`ui::Dataspace::TRANSFER_SMPTE_170M`, 22.`ui::Dataspace::TRANSFER_GAMMA2_2`, 23.`ui::Dataspace::TRANSFER_GAMMA2_6`, 24.`ui::Dataspace::TRANSFER_GAMMA2_8`, 25.`ui::Dataspace::TRANSFER_ST2084`, 26.`ui::Dataspace::TRANSFER_HLG`, 27.`ui::Dataspace::RANGE_SHIFT`, 28.`ui::Dataspace::RANGE_MASK`, 29.`ui::Dataspace::RANGE_UNSPECIFIED`, 30.`ui::Dataspace::RANGE_FULL`, 31.`ui::Dataspace::RANGE_LIMITED`, 32.`ui::Dataspace::RANGE_EXTENDED`, 33.`ui::Dataspace::SRGB_LINEAR`, 34.`ui::Dataspace::V0_SRGB_LINEAR`, 35.`ui::Dataspace::V0_SCRGB_LINEAR`, 36.`ui::Dataspace::SRGB`, 37.`ui::Dataspace::V0_SRGB`, 38.`ui::Dataspace::V0_SCRGB`, 39.`ui::Dataspace::JFIF`, 40.`ui::Dataspace::V0_JFIF`, 41.`ui::Dataspace::BT601_625`, 42.`ui::Dataspace::V0_BT601_625`, 43.`ui::Dataspace::BT601_525`, 44.`ui::Dataspace::V0_BT601_525`, 45.`ui::Dataspace::BT709`, 46.`ui::Dataspace::V0_BT709`, 47.`ui::Dataspace::DCI_P3_LINEAR`, 48.`ui::Dataspace::DCI_P3`, 49.`ui::Dataspace::DISPLAY_P3_LINEAR`, 50.`ui::Dataspace::DISPLAY_P3`, 51.`ui::Dataspace::ADOBE_RGB`, 52.`ui::Dataspace::BT2020_LINEAR`, 53.`ui::Dataspace::BT2020`, 54.`ui::Dataspace::BT2020_PQ`, 55.`ui::Dataspace::DEPTH`, 56.`ui::Dataspace::SENSOR`, 57.`ui::Dataspace::BT2020_ITU`, 58.`ui::Dataspace::BT2020_ITU_PQ`, 59.`ui::Dataspace::BT2020_ITU_HLG`, 60.`ui::Dataspace::BT2020_HLG`, 61.`ui::Dataspace::DISPLAY_BT2020`, 62.`ui::Dataspace::DYNAMIC_DEPTH`, 63.`ui::Dataspace::JPEG_APP_SEGMENTS`, 64.`ui::Dataspace::HEIF`, |Value obtained from FuzzedDataProvider|
-|`transform`| 0.`ui::Transform::ROT_0`, 1.`ui::Transform::FLIP_H`, 2.`ui::Transform::FLIP_V`, 3.`ui::Transform::ROT_90`, 4.`ui::Transform::ROT_180`, 5.`ui::Transform::ROT_270` |Value obtained from FuzzedDataProvider|
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) libgui_surfaceComposerClient_fuzzer
-```
-2. To run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/libgui_surfaceComposerClient_fuzzer/libgui_surfaceComposerClient_fuzzer
-```
-
-# <a name="libgui_parcelable_fuzzer"></a> Fuzzer for Libgui_Parcelable
-
-Libgui_Parcelable supports the following parameters:
-1. LayerMetadataKey (parameter name:`key`)
-2. Dataspace (parameter name:`mDataspace`)
-
-| Parameter| Valid Values| Configured Value|
-|------------- |-------------| ----- |
-|`key`| 0.`view::LayerMetadataKey::METADATA_OWNER_UID`, 1.`view::LayerMetadataKey::METADATA_WINDOW_TYPE`, 2.`view::LayerMetadataKey::METADATA_TASK_ID`, 3.`view::LayerMetadataKey::METADATA_MOUSE_CURSOR`, 4.`view::LayerMetadataKey::METADATA_ACCESSIBILITY_ID`, 5.`view::LayerMetadataKey::METADATA_OWNER_PID`, 6.`view::LayerMetadataKey::METADATA_DEQUEUE_TIME`, 7.`view::LayerMetadataKey::METADATA_GAME_MODE`, |Value obtained from FuzzedDataProvider|
-|`mDataSpace`| 0.`ui::Dataspace::UNKNOWN`, 1.`ui::Dataspace::ARBITRARY`, 2.`ui::Dataspace::STANDARD_SHIFT`, 3.`ui::Dataspace::STANDARD_MASK`, 4.`ui::Dataspace::STANDARD_UNSPECIFIED`, 5.`ui::Dataspace::STANDARD_BT709`, 6.`ui::Dataspace::STANDARD_BT601_625`, 7.`ui::Dataspace::STANDARD_BT601_625_UNADJUSTED`, 8.`ui::Dataspace::STANDARD_BT601_525`, 9.`ui::Dataspace::STANDARD_BT601_525_UNADJUSTED`, 10.`ui::Dataspace::STANDARD_BT2020`, 11.`ui::Dataspace::STANDARD_BT2020_CONSTANT_LUMINANCE`, 12.`ui::Dataspace::STANDARD_BT470M`, 13.`ui::Dataspace::STANDARD_FILM`, 14.`ui::Dataspace::STANDARD_DCI_P3`, 15.`ui::Dataspace::STANDARD_ADOBE_RGB`, 16.`ui::Dataspace::TRANSFER_SHIFT`, 17.`ui::Dataspace::TRANSFER_MASK`, 18.`ui::Dataspace::TRANSFER_UNSPECIFIED`, 19.`ui::Dataspace::TRANSFER_LINEAR`, 20.`ui::Dataspace::TRANSFER_SRGB`, 21.`ui::Dataspace::TRANSFER_SMPTE_170M`, 22.`ui::Dataspace::TRANSFER_GAMMA2_2`, 23.`ui::Dataspace::TRANSFER_GAMMA2_6`, 24.`ui::Dataspace::TRANSFER_GAMMA2_8`, 25.`ui::Dataspace::TRANSFER_ST2084`, 26.`ui::Dataspace::TRANSFER_HLG`, 27.`ui::Dataspace::RANGE_SHIFT`, 28.`ui::Dataspace::RANGE_MASK`, 29.`ui::Dataspace::RANGE_UNSPECIFIED`, 30.`ui::Dataspace::RANGE_FULL`, 31.`ui::Dataspace::RANGE_LIMITED`, 32.`ui::Dataspace::RANGE_EXTENDED`, 33.`ui::Dataspace::SRGB_LINEAR`, 34.`ui::Dataspace::V0_SRGB_LINEAR`, 35.`ui::Dataspace::V0_SCRGB_LINEAR`, 36.`ui::Dataspace::SRGB`, 37.`ui::Dataspace::V0_SRGB`, 38.`ui::Dataspace::V0_SCRGB`, 39.`ui::Dataspace::JFIF`, 40.`ui::Dataspace::V0_JFIF`, 41.`ui::Dataspace::BT601_625`, 42.`ui::Dataspace::V0_BT601_625`, 43.`ui::Dataspace::BT601_525`, 44.`ui::Dataspace::V0_BT601_525`, 45.`ui::Dataspace::BT709`, 46.`ui::Dataspace::V0_BT709`, 47.`ui::Dataspace::DCI_P3_LINEAR`, 48.`ui::Dataspace::DCI_P3`, 49.`ui::Dataspace::DISPLAY_P3_LINEAR`, 50.`ui::Dataspace::DISPLAY_P3`, 51.`ui::Dataspace::ADOBE_RGB`, 52.`ui::Dataspace::BT2020_LINEAR`, 53.`ui::Dataspace::BT2020`, 54.`ui::Dataspace::BT2020_PQ`, 55.`ui::Dataspace::DEPTH`, 56.`ui::Dataspace::SENSOR`, 57.`ui::Dataspace::BT2020_ITU`, 58.`ui::Dataspace::BT2020_ITU_PQ`, 59.`ui::Dataspace::BT2020_ITU_HLG`, 60.`ui::Dataspace::BT2020_HLG`, 61.`ui::Dataspace::DISPLAY_BT2020`, 62.`ui::Dataspace::DYNAMIC_DEPTH`, 63.`ui::Dataspace::JPEG_APP_SEGMENTS`, 64.`ui::Dataspace::HEIF`, |Value obtained from FuzzedDataProvider|
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) libgui_fuzzer
-```
-2. Run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/libgui_fuzzer/libgui_fuzzer
-```
-
-# <a name="libgui_bufferQueue_fuzzer"></a> Fuzzer for BufferQueue
-
-BufferQueue supports the following parameters:
-1. SurfaceWidth (parameter name:`width`)
-2. SurfaceHeight (parameter name:`height`)
-3. TransactionStateFlags (parameter name:`flags`)
-4. TransformHint (parameter name:`outTransformHint`)
-5. SurfacePixelFormat (parameter name:`format`)
-6. LayerId (parameter name:`layerId`)
-7. BufferId (parameter name:`bufferId`)
-8. FrameNumber (parameter name:`frameNumber`)
-9. FrameRate (parameter name:`frameRate`)
-10. Compatability (parameter name:`compatability`)
-11. LatchTime (parameter name:`latchTime`)
-12. AcquireTime (parameter name:`acquireTime`)
-13. RefreshTime (parameter name:`refreshTime`)
-14. DequeueTime (parameter name:`dequeueTime`)
-15. Slot (parameter name:`slot`)
-16. MaxBuffers (parameter name:`maxBuffers`)
-17. GenerationNumber (parameter name:`generationNumber`)
-18. Api (parameter name:`api`)
-19. Usage (parameter name:`usage`)
-20. MaxFrameNumber (parameter name:`maxFrameNumber`)
-21. BufferCount (parameter name:`bufferCount`)
-22. MaxAcquredBufferCount (parameter name:`maxAcquredBufferCount`)
-23. Status (parameter name:`status`)
-24. ApiConnection (parameter name:`apiConnection`)
-25. Dataspace (parameter name:`dataspace`)
-
-| Parameter| Valid Values| Configured Value|
-|------------- |-------------| ----- |
-|`status`| 0.`OK`, 1.`NO_MEMORY`, 2.`NO_INIT`, 3.`BAD_VALUE`, 4.`DEAD_OBJECT`, 5.`INVALID_OPERATION`, 6.`TIMED_OUT`, 7.`WOULD_BLOCK`, 8.`UNKNOWN_ERROR`, 9.`ALREADY_EXISTS`, |Value obtained from FuzzedDataProvider|
-|`apiConnection`| 0.`BufferQueueCore::CURRENTLY_CONNECTED_API`, 1.`BufferQueueCore::NO_CONNECTED_API`, 2.`NATIVE_WINDOW_API_EGL`, 3.`NATIVE_WINDOW_API_CPU`, 4.`NATIVE_WINDOW_API_MEDIA`, 5.`NATIVE_WINDOW_API_CAMERA`, |Value obtained from FuzzedDataProvider|
-|`dataspace`| 0.`ui::Dataspace::UNKNOWN`, 1.`ui::Dataspace::ARBITRARY`, 2.`ui::Dataspace::STANDARD_SHIFT`, 3.`ui::Dataspace::STANDARD_MASK`, 4.`ui::Dataspace::STANDARD_UNSPECIFIED`, 5.`ui::Dataspace::STANDARD_BT709`, 6.`ui::Dataspace::STANDARD_BT601_625`, 7.`ui::Dataspace::STANDARD_BT601_625_UNADJUSTED`, 8.`ui::Dataspace::STANDARD_BT601_525`, 9.`ui::Dataspace::STANDARD_BT601_525_UNADJUSTED`, 10.`ui::Dataspace::STANDARD_BT2020`, 11.`ui::Dataspace::STANDARD_BT2020_CONSTANT_LUMINANCE`, 12.`ui::Dataspace::STANDARD_BT470M`, 13.`ui::Dataspace::STANDARD_FILM`, 14.`ui::Dataspace::STANDARD_DCI_P3`, 15.`ui::Dataspace::STANDARD_ADOBE_RGB`, 16.`ui::Dataspace::TRANSFER_SHIFT`, 17.`ui::Dataspace::TRANSFER_MASK`, 18.`ui::Dataspace::TRANSFER_UNSPECIFIED`, 19.`ui::Dataspace::TRANSFER_LINEAR`, 20.`ui::Dataspace::TRANSFER_SRGB`, 21.`ui::Dataspace::TRANSFER_SMPTE_170M`, 22.`ui::Dataspace::TRANSFER_GAMMA2_2`, 23.`ui::Dataspace::TRANSFER_GAMMA2_6`, 24.`ui::Dataspace::TRANSFER_GAMMA2_8`, 25.`ui::Dataspace::TRANSFER_ST2084`, 26.`ui::Dataspace::TRANSFER_HLG`, 27.`ui::Dataspace::RANGE_SHIFT`, 28.`ui::Dataspace::RANGE_MASK`, 29.`ui::Dataspace::RANGE_UNSPECIFIED`, 30.`ui::Dataspace::RANGE_FULL`, 31.`ui::Dataspace::RANGE_LIMITED`, 32.`ui::Dataspace::RANGE_EXTENDED`, 33.`ui::Dataspace::SRGB_LINEAR`, 34.`ui::Dataspace::V0_SRGB_LINEAR`, 35.`ui::Dataspace::V0_SCRGB_LINEAR`, 36.`ui::Dataspace::SRGB`, 37.`ui::Dataspace::V0_SRGB`, 38.`ui::Dataspace::V0_SCRGB`, 39.`ui::Dataspace::JFIF`, 40.`ui::Dataspace::V0_JFIF`, 41.`ui::Dataspace::BT601_625`, 42.`ui::Dataspace::V0_BT601_625`, 43.`ui::Dataspace::BT601_525`, 44.`ui::Dataspace::V0_BT601_525`, 45.`ui::Dataspace::BT709`, 46.`ui::Dataspace::V0_BT709`, 47.`ui::Dataspace::DCI_P3_LINEAR`, 48.`ui::Dataspace::DCI_P3`, 49.`ui::Dataspace::DISPLAY_P3_LINEAR`, 50.`ui::Dataspace::DISPLAY_P3`, 51.`ui::Dataspace::ADOBE_RGB`, 52.`ui::Dataspace::BT2020_LINEAR`, 53.`ui::Dataspace::BT2020`, 54.`ui::Dataspace::BT2020_PQ`, 55.`ui::Dataspace::DEPTH`, 56.`ui::Dataspace::SENSOR`, 57.`ui::Dataspace::BT2020_ITU`, 58.`ui::Dataspace::BT2020_ITU_PQ`, 59.`ui::Dataspace::BT2020_ITU_HLG`, 60.`ui::Dataspace::BT2020_HLG`, 61.`ui::Dataspace::DISPLAY_BT2020`, 62.`ui::Dataspace::DYNAMIC_DEPTH`, 63.`ui::Dataspace::JPEG_APP_SEGMENTS`, 64.`ui::Dataspace::HEIF`, |Value obtained from FuzzedDataProvider|
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) libgui_bufferQueue_fuzzer
-```
-2. To run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/libgui_bufferQueue_fuzzer/libgui_bufferQueue_fuzzer
-```
-
-# <a name="libgui_consumer_fuzzer"></a> Fuzzer for Libgui_Consumer
-
-Libgui_Consumer supports the following parameters:
-1. GraphicWidth (parameter name:`graphicWidth`)
-2. GraphicHeight (parameter name:`graphicHeight`)
-4. TransformHint (parameter name:`outTransformHint`)
-5. GraphicPixelFormat (parameter name:`format`)
-6. Usage (parameter name:`usage`)
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) libgui_consumer_fuzzer
-```
-2. Run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/libgui_consumer_fuzzer/libgui_consumer_fuzzer
-```
-
-# <a name="libgui_displayEvent_fuzzer"></a> Fuzzer for LibGui_DisplayEvent
-
-LibGui_DisplayEvent supports the following parameters:
-1. DisplayEventType (parameter name:`type`)
-2. Events (parameter name:`events`)
-3. VsyncSource (parameter name:`vsyncSource`)
-4. EventRegistrationFlags (parameter name:`flags`)
-
-| Parameter| Valid Values| Configured Value|
-|------------- |-------------| ----- |
-|`vsyncSource`| 0.`ISurfaceComposer::eVsyncSourceApp`, 1.`ISurfaceComposer::eVsyncSourceSurfaceFlinger`, |Value obtained from FuzzedDataProvider|
-|`flags`| 0.`ISurfaceComposer::EventRegistration::modeChanged`, 1.`ISurfaceComposer::EventRegistration::frameRateOverride`, |Value obtained from FuzzedDataProvider|
-|`type`| 0.`DisplayEventReceiver::DISPLAY_EVENT_NULL`, 1.`DisplayEventReceiver::DISPLAY_EVENT_VSYNC`, 2.`DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG`, 3.`DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE`, 4.`DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE`, 5.`DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH`, |Value obtained from FuzzedDataProvider|
-|`events`| 0.`Looper::EVENT_INPUT`, 1.`Looper::EVENT_OUTPUT`, 2.`Looper::EVENT_ERROR`, 3.`Looper::EVENT_HANGUP`, 4.`Looper::EVENT_INVALID`, |Value obtained from FuzzedDataProvider|
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) libgui_displayEvent_fuzzer
-```
-2. Run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/libgui_displayEvent_fuzzer/libgui_displayEvent_fuzzer
-```
diff --git a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
deleted file mode 100644
index 2e270b7..0000000
--- a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <android-base/stringprintf.h>
-#include <gui/BufferQueueConsumer.h>
-#include <gui/BufferQueueCore.h>
-#include <gui/BufferQueueProducer.h>
-#include <gui/bufferqueue/2.0/types.h>
-#include <system/window.h>
-
-#include <libgui_fuzzer_utils.h>
-
-using namespace android;
-using namespace hardware::graphics::bufferqueue;
-using namespace V1_0::utils;
-using namespace V2_0::utils;
-
-constexpr int32_t kMaxBytes = 256;
-
-constexpr int32_t kError[] = {
-        OK,        NO_MEMORY,   NO_INIT,       BAD_VALUE,      DEAD_OBJECT, INVALID_OPERATION,
-        TIMED_OUT, WOULD_BLOCK, UNKNOWN_ERROR, ALREADY_EXISTS,
-};
-
-constexpr int32_t kAPIConnection[] = {
-        BufferQueueCore::CURRENTLY_CONNECTED_API,
-        BufferQueueCore::NO_CONNECTED_API,
-        NATIVE_WINDOW_API_EGL,
-        NATIVE_WINDOW_API_CPU,
-        NATIVE_WINDOW_API_MEDIA,
-        NATIVE_WINDOW_API_CAMERA,
-};
-
-class BufferQueueFuzzer {
-public:
-    BufferQueueFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    void invokeTypes();
-    void invokeH2BGraphicBufferV1();
-    void invokeH2BGraphicBufferV2();
-    void invokeBufferQueueConsumer();
-    void invokeBufferQueueProducer();
-    void invokeBlastBufferQueue();
-    void invokeQuery(sp<BufferQueueProducer>);
-    void invokeQuery(sp<V1_0::utils::H2BGraphicBufferProducer>);
-    void invokeQuery(sp<V2_0::utils::H2BGraphicBufferProducer>);
-    void invokeAcquireBuffer(sp<BufferQueueConsumer>);
-    void invokeOccupancyTracker(sp<BufferQueueConsumer>);
-    sp<SurfaceControl> makeSurfaceControl();
-    sp<BLASTBufferQueue> makeBLASTBufferQueue(sp<SurfaceControl>);
-
-    FuzzedDataProvider mFdp;
-};
-
-class ManageResourceHandle {
-public:
-    ManageResourceHandle(FuzzedDataProvider* fdp) {
-        mNativeHandle = native_handle_create(0 /*numFds*/, 1 /*numInts*/);
-        mShouldOwn = fdp->ConsumeBool();
-        mStream = NativeHandle::create(mNativeHandle, mShouldOwn);
-    }
-    ~ManageResourceHandle() {
-        if (!mShouldOwn) {
-            native_handle_close(mNativeHandle);
-            native_handle_delete(mNativeHandle);
-        }
-    }
-    sp<NativeHandle> getStream() { return mStream; }
-
-private:
-    bool mShouldOwn;
-    sp<NativeHandle> mStream;
-    native_handle_t* mNativeHandle;
-};
-
-sp<SurfaceControl> BufferQueueFuzzer::makeSurfaceControl() {
-    sp<IBinder> handle;
-    const sp<FakeBnSurfaceComposerClient> testClient(new FakeBnSurfaceComposerClient());
-    sp<SurfaceComposerClient> client = new SurfaceComposerClient(testClient);
-    sp<BnGraphicBufferProducer> producer;
-    uint32_t layerId = mFdp.ConsumeIntegral<uint32_t>();
-    std::string layerName = base::StringPrintf("#%d", layerId);
-    return sp<SurfaceControl>::make(client, handle, layerId, layerName,
-                                    mFdp.ConsumeIntegral<int32_t>(),
-                                    mFdp.ConsumeIntegral<uint32_t>(),
-                                    mFdp.ConsumeIntegral<int32_t>(),
-                                    mFdp.ConsumeIntegral<uint32_t>(),
-                                    mFdp.ConsumeIntegral<uint32_t>());
-}
-
-sp<BLASTBufferQueue> BufferQueueFuzzer::makeBLASTBufferQueue(sp<SurfaceControl> surface) {
-    return sp<BLASTBufferQueue>::make(mFdp.ConsumeRandomLengthString(kMaxBytes), surface,
-                                      mFdp.ConsumeIntegral<uint32_t>(),
-                                      mFdp.ConsumeIntegral<uint32_t>(),
-                                      mFdp.ConsumeIntegral<int32_t>());
-}
-
-void BufferQueueFuzzer::invokeBlastBufferQueue() {
-    sp<SurfaceControl> surface = makeSurfaceControl();
-    sp<BLASTBufferQueue> queue = makeBLASTBufferQueue(surface);
-
-    BufferItem item;
-    queue->onFrameAvailable(item);
-    queue->onFrameReplaced(item);
-    uint64_t bufferId = mFdp.ConsumeIntegral<uint64_t>();
-    queue->onFrameDequeued(bufferId);
-    queue->onFrameCancelled(bufferId);
-
-    SurfaceComposerClient::Transaction next;
-    uint64_t frameNumber = mFdp.ConsumeIntegral<uint64_t>();
-    queue->mergeWithNextTransaction(&next, frameNumber);
-    queue->applyPendingTransactions(frameNumber);
-
-    queue->update(surface, mFdp.ConsumeIntegral<uint32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
-                  mFdp.ConsumeIntegral<int32_t>());
-    queue->setFrameRate(mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeIntegral<int8_t>(),
-                        mFdp.ConsumeBool() /*shouldBeSeamless*/);
-    FrameTimelineInfo info;
-    queue->setFrameTimelineInfo(mFdp.ConsumeIntegral<uint64_t>(), info);
-
-    ManageResourceHandle handle(&mFdp);
-    queue->setSidebandStream(handle.getStream());
-
-    queue->getLastTransformHint();
-    queue->getLastAcquiredFrameNum();
-
-    CompositorTiming compTiming;
-    sp<Fence> previousFence = new Fence(memfd_create("pfd", MFD_ALLOW_SEALING));
-    sp<Fence> gpuFence = new Fence(memfd_create("gfd", MFD_ALLOW_SEALING));
-    FrameEventHistoryStats frameStats(frameNumber, mFdp.ConsumeIntegral<uint64_t>(), gpuFence,
-                                      compTiming, mFdp.ConsumeIntegral<int64_t>(),
-                                      mFdp.ConsumeIntegral<int64_t>());
-    std::vector<SurfaceControlStats> stats;
-    sp<Fence> presentFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
-    SurfaceControlStats controlStats(surface, mFdp.ConsumeIntegral<int64_t>(),
-                                     mFdp.ConsumeIntegral<int64_t>(), presentFence, previousFence,
-                                     mFdp.ConsumeIntegral<uint32_t>(), frameStats,
-                                     mFdp.ConsumeIntegral<uint32_t>());
-    stats.push_back(controlStats);
-}
-
-void BufferQueueFuzzer::invokeQuery(sp<BufferQueueProducer> producer) {
-    int32_t value;
-    producer->query(mFdp.ConsumeIntegral<int32_t>(), &value);
-}
-
-void BufferQueueFuzzer::invokeQuery(sp<V1_0::utils::H2BGraphicBufferProducer> producer) {
-    int32_t value;
-    producer->query(mFdp.ConsumeIntegral<int32_t>(), &value);
-}
-
-void BufferQueueFuzzer::invokeQuery(sp<V2_0::utils::H2BGraphicBufferProducer> producer) {
-    int32_t value;
-    producer->query(mFdp.ConsumeIntegral<int32_t>(), &value);
-}
-
-void BufferQueueFuzzer::invokeBufferQueueProducer() {
-    sp<BufferQueueCore> core(new BufferQueueCore());
-    sp<BufferQueueProducer> producer(new BufferQueueProducer(core));
-    const sp<android::IProducerListener> listener;
-    android::IGraphicBufferProducer::QueueBufferOutput output;
-    uint32_t api = mFdp.ConsumeIntegral<uint32_t>();
-    producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output);
-
-    sp<GraphicBuffer> buffer;
-    int32_t slot = mFdp.ConsumeIntegral<int32_t>();
-    uint32_t maxBuffers = mFdp.ConsumeIntegral<uint32_t>();
-    producer->requestBuffer(slot, &buffer);
-    producer->setMaxDequeuedBufferCount(maxBuffers);
-    producer->setAsyncMode(mFdp.ConsumeBool() /*async*/);
-
-    android::IGraphicBufferProducer::QueueBufferInput input;
-    producer->attachBuffer(&slot, buffer);
-    producer->queueBuffer(slot, input, &output);
-
-    int32_t format = mFdp.ConsumeIntegral<int32_t>();
-    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
-    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
-    uint64_t usage = mFdp.ConsumeIntegral<uint64_t>();
-    uint64_t outBufferAge;
-    FrameEventHistoryDelta outTimestamps;
-    sp<android::Fence> fence;
-    producer->dequeueBuffer(&slot, &fence, width, height, format, usage, &outBufferAge,
-                            &outTimestamps);
-    producer->detachBuffer(slot);
-    producer->detachNextBuffer(&buffer, &fence);
-    producer->cancelBuffer(slot, fence);
-
-    invokeQuery(producer);
-
-    ManageResourceHandle handle(&mFdp);
-    producer->setSidebandStream(handle.getStream());
-
-    producer->allocateBuffers(width, height, format, usage);
-    producer->allowAllocation(mFdp.ConsumeBool() /*allow*/);
-    producer->setSharedBufferMode(mFdp.ConsumeBool() /*sharedBufferMode*/);
-    producer->setAutoRefresh(mFdp.ConsumeBool() /*autoRefresh*/);
-    producer->setLegacyBufferDrop(mFdp.ConsumeBool() /*drop*/);
-    producer->setAutoPrerotation(mFdp.ConsumeBool() /*autoPrerotation*/);
-
-    producer->setGenerationNumber(mFdp.ConsumeIntegral<uint32_t>());
-    producer->setDequeueTimeout(mFdp.ConsumeIntegral<uint32_t>());
-    producer->disconnect(api);
-}
-
-void BufferQueueFuzzer::invokeAcquireBuffer(sp<BufferQueueConsumer> consumer) {
-    BufferItem item;
-    consumer->acquireBuffer(&item, mFdp.ConsumeIntegral<uint32_t>(),
-                            mFdp.ConsumeIntegral<uint64_t>());
-}
-
-void BufferQueueFuzzer::invokeOccupancyTracker(sp<BufferQueueConsumer> consumer) {
-    String8 outResult;
-    String8 prefix((mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str());
-    consumer->dumpState(prefix, &outResult);
-
-    std::vector<OccupancyTracker::Segment> outHistory;
-    consumer->getOccupancyHistory(mFdp.ConsumeBool() /*forceFlush*/, &outHistory);
-}
-
-void BufferQueueFuzzer::invokeBufferQueueConsumer() {
-    sp<BufferQueueCore> core(new BufferQueueCore());
-    sp<BufferQueueConsumer> consumer(new BufferQueueConsumer(core));
-    sp<android::IConsumerListener> listener;
-    consumer->consumerConnect(listener, mFdp.ConsumeBool() /*controlledByApp*/);
-    invokeAcquireBuffer(consumer);
-
-    int32_t slot = mFdp.ConsumeIntegral<int32_t>();
-    sp<GraphicBuffer> buffer =
-            new GraphicBuffer(mFdp.ConsumeIntegral<uint32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
-                              mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
-                              mFdp.ConsumeIntegral<uint64_t>());
-    consumer->attachBuffer(&slot, buffer);
-    consumer->detachBuffer(slot);
-
-    consumer->setDefaultBufferSize(mFdp.ConsumeIntegral<uint32_t>(),
-                                   mFdp.ConsumeIntegral<uint32_t>());
-    consumer->setMaxBufferCount(mFdp.ConsumeIntegral<int32_t>());
-    consumer->setMaxAcquiredBufferCount(mFdp.ConsumeIntegral<int32_t>());
-
-    String8 name((mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str());
-    consumer->setConsumerName(name);
-    consumer->setDefaultBufferFormat(mFdp.ConsumeIntegral<int32_t>());
-    android_dataspace dataspace =
-            static_cast<android_dataspace>(mFdp.PickValueInArray(kDataspaces));
-    consumer->setDefaultBufferDataSpace(dataspace);
-
-    consumer->setTransformHint(mFdp.ConsumeIntegral<uint32_t>());
-    consumer->setConsumerUsageBits(mFdp.ConsumeIntegral<uint64_t>());
-    consumer->setConsumerIsProtected(mFdp.ConsumeBool() /*isProtected*/);
-    invokeOccupancyTracker(consumer);
-
-    sp<Fence> releaseFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
-    consumer->releaseBuffer(mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<uint64_t>(),
-                            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, releaseFence);
-    consumer->consumerDisconnect();
-}
-
-void BufferQueueFuzzer::invokeTypes() {
-    HStatus hStatus;
-    int32_t status = mFdp.PickValueInArray(kError);
-    bool bufferNeedsReallocation = mFdp.ConsumeBool();
-    bool releaseAllBuffers = mFdp.ConsumeBool();
-    b2h(status, &hStatus, &bufferNeedsReallocation, &releaseAllBuffers);
-    h2b(hStatus, &status);
-
-    HConnectionType type;
-    int32_t apiConnection = mFdp.PickValueInArray(kAPIConnection);
-    b2h(apiConnection, &type);
-    h2b(type, &apiConnection);
-}
-
-void BufferQueueFuzzer::invokeH2BGraphicBufferV1() {
-    sp<V1_0::utils::H2BGraphicBufferProducer> producer(
-            new V1_0::utils::H2BGraphicBufferProducer(new FakeGraphicBufferProducerV1()));
-    const sp<android::IProducerListener> listener;
-    android::IGraphicBufferProducer::QueueBufferOutput output;
-    uint32_t api = mFdp.ConsumeIntegral<uint32_t>();
-    producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output);
-
-    sp<GraphicBuffer> buffer;
-    int32_t slot = mFdp.ConsumeIntegral<int32_t>();
-    producer->requestBuffer(slot, &buffer);
-    producer->setMaxDequeuedBufferCount(mFdp.ConsumeIntegral<int32_t>());
-    producer->setAsyncMode(mFdp.ConsumeBool());
-
-    android::IGraphicBufferProducer::QueueBufferInput input;
-    input.fence = new Fence(memfd_create("ffd", MFD_ALLOW_SEALING));
-    producer->attachBuffer(&slot, buffer);
-    producer->queueBuffer(slot, input, &output);
-
-    int32_t format = mFdp.ConsumeIntegral<int32_t>();
-    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
-    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
-    uint64_t usage = mFdp.ConsumeIntegral<uint64_t>();
-    uint64_t outBufferAge;
-    FrameEventHistoryDelta outTimestamps;
-    sp<android::Fence> fence;
-    producer->dequeueBuffer(&slot, &fence, width, height, format, usage, &outBufferAge,
-                            &outTimestamps);
-    producer->detachBuffer(slot);
-    producer->cancelBuffer(slot, fence);
-
-    invokeQuery(producer);
-
-    ManageResourceHandle handle(&mFdp);
-    producer->setSidebandStream(handle.getStream());
-
-    producer->allocateBuffers(width, height, format, usage);
-    producer->allowAllocation(mFdp.ConsumeBool() /*allow*/);
-    producer->setSharedBufferMode(mFdp.ConsumeBool() /*sharedBufferMode*/);
-    producer->setAutoRefresh(mFdp.ConsumeBool() /*autoRefresh*/);
-
-    producer->setGenerationNumber(mFdp.ConsumeIntegral<uint32_t>());
-    producer->setDequeueTimeout(mFdp.ConsumeIntegral<uint32_t>());
-    producer->disconnect(api);
-}
-
-void BufferQueueFuzzer::invokeH2BGraphicBufferV2() {
-    sp<V2_0::utils::H2BGraphicBufferProducer> producer(
-            new V2_0::utils::H2BGraphicBufferProducer(new FakeGraphicBufferProducerV2()));
-    const sp<android::IProducerListener> listener;
-    android::IGraphicBufferProducer::QueueBufferOutput output;
-    uint32_t api = mFdp.ConsumeIntegral<uint32_t>();
-    producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output);
-
-    sp<GraphicBuffer> buffer;
-    int32_t slot = mFdp.ConsumeIntegral<int32_t>();
-    producer->requestBuffer(slot, &buffer);
-    producer->setMaxDequeuedBufferCount(mFdp.ConsumeIntegral<uint32_t>());
-    producer->setAsyncMode(mFdp.ConsumeBool());
-
-    android::IGraphicBufferProducer::QueueBufferInput input;
-    input.fence = new Fence(memfd_create("ffd", MFD_ALLOW_SEALING));
-    producer->attachBuffer(&slot, buffer);
-    producer->queueBuffer(slot, input, &output);
-
-    int32_t format = mFdp.ConsumeIntegral<int32_t>();
-    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
-    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
-    uint64_t usage = mFdp.ConsumeIntegral<uint64_t>();
-    uint64_t outBufferAge;
-    FrameEventHistoryDelta outTimestamps;
-    sp<android::Fence> fence;
-    producer->dequeueBuffer(&slot, &fence, width, height, format, usage, &outBufferAge,
-                            &outTimestamps);
-    producer->detachBuffer(slot);
-    producer->cancelBuffer(slot, fence);
-
-    invokeQuery(producer);
-
-    ManageResourceHandle handle(&mFdp);
-    producer->setSidebandStream(handle.getStream());
-
-    producer->allocateBuffers(width, height, format, usage);
-    producer->allowAllocation(mFdp.ConsumeBool() /*allow*/);
-    producer->setSharedBufferMode(mFdp.ConsumeBool() /*sharedBufferMode*/);
-    producer->setAutoRefresh(mFdp.ConsumeBool() /*autoRefresh*/);
-
-    producer->setGenerationNumber(mFdp.ConsumeIntegral<uint32_t>());
-    producer->setDequeueTimeout(mFdp.ConsumeIntegral<uint32_t>());
-    producer->disconnect(api);
-}
-
-void BufferQueueFuzzer::process() {
-    invokeBlastBufferQueue();
-    invokeH2BGraphicBufferV1();
-    invokeH2BGraphicBufferV2();
-    invokeTypes();
-    invokeBufferQueueConsumer();
-    invokeBufferQueueProducer();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    BufferQueueFuzzer bufferQueueFuzzer(data, size);
-    bufferQueueFuzzer.process();
-    return 0;
-}
diff --git a/libs/gui/fuzzer/libgui_consumer_fuzzer.cpp b/libs/gui/fuzzer/libgui_consumer_fuzzer.cpp
deleted file mode 100644
index 24a046d..0000000
--- a/libs/gui/fuzzer/libgui_consumer_fuzzer.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <gui/BufferQueueConsumer.h>
-#include <gui/BufferQueueCore.h>
-#include <gui/BufferQueueProducer.h>
-#include <gui/GLConsumer.h>
-#include <libgui_fuzzer_utils.h>
-
-using namespace android;
-
-constexpr int32_t kMinBuffer = 0;
-constexpr int32_t kMaxBuffer = 100000;
-
-class ConsumerFuzzer {
-public:
-    ConsumerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-void ConsumerFuzzer::process() {
-    sp<BufferQueueCore> core(new BufferQueueCore());
-    sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
-
-    uint64_t maxBuffers = mFdp.ConsumeIntegralInRange<uint64_t>(kMinBuffer, kMaxBuffer);
-    sp<CpuConsumer> cpu(
-            new CpuConsumer(consumer, maxBuffers, mFdp.ConsumeBool() /*controlledByApp*/));
-    CpuConsumer::LockedBuffer lockBuffer;
-    cpu->lockNextBuffer(&lockBuffer);
-    cpu->unlockBuffer(lockBuffer);
-    cpu->abandon();
-
-    uint32_t tex = mFdp.ConsumeIntegral<uint32_t>();
-    sp<GLConsumer> glComsumer(new GLConsumer(consumer, tex, GLConsumer::TEXTURE_EXTERNAL,
-                                             mFdp.ConsumeBool() /*useFenceSync*/,
-                                             mFdp.ConsumeBool() /*isControlledByApp*/));
-    sp<Fence> releaseFence = new Fence(memfd_create("rfd", MFD_ALLOW_SEALING));
-    glComsumer->setReleaseFence(releaseFence);
-    glComsumer->updateTexImage();
-    glComsumer->releaseTexImage();
-
-    sp<GraphicBuffer> buffer =
-            new GraphicBuffer(mFdp.ConsumeIntegral<uint32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
-                              mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
-                              mFdp.ConsumeIntegral<uint64_t>());
-    float mtx[16];
-    glComsumer->getTransformMatrix(mtx);
-    glComsumer->computeTransformMatrix(mtx, buffer, getRect(&mFdp),
-                                       mFdp.ConsumeIntegral<uint32_t>(),
-                                       mFdp.ConsumeBool() /*filtering*/);
-    glComsumer->scaleDownCrop(getRect(&mFdp), mFdp.ConsumeIntegral<uint32_t>(),
-                              mFdp.ConsumeIntegral<uint32_t>());
-
-    glComsumer->setDefaultBufferSize(mFdp.ConsumeIntegral<uint32_t>(),
-                                     mFdp.ConsumeIntegral<uint32_t>());
-    glComsumer->setFilteringEnabled(mFdp.ConsumeBool() /*enabled*/);
-
-    glComsumer->setConsumerUsageBits(mFdp.ConsumeIntegral<uint64_t>());
-    glComsumer->attachToContext(tex);
-    glComsumer->abandon();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    ConsumerFuzzer consumerFuzzer(data, size);
-    consumerFuzzer.process();
-    return 0;
-}
diff --git a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
deleted file mode 100644
index 0d2a52b..0000000
--- a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <android/gui/ISurfaceComposer.h>
-
-#include <libgui_fuzzer_utils.h>
-
-using namespace android;
-
-constexpr gui::ISurfaceComposer::VsyncSource kVsyncSource[] = {
-        gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
-        gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger,
-};
-
-constexpr gui::ISurfaceComposer::EventRegistration kEventRegistration[] = {
-        gui::ISurfaceComposer::EventRegistration::modeChanged,
-        gui::ISurfaceComposer::EventRegistration::frameRateOverride,
-};
-
-constexpr uint32_t kDisplayEvent[] = {
-        DisplayEventReceiver::DISPLAY_EVENT_NULL,
-        DisplayEventReceiver::DISPLAY_EVENT_VSYNC,
-        DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG,
-        DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE,
-        DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
-        DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH,
-};
-
-constexpr int32_t kEvents[] = {
-        Looper::EVENT_INPUT,  Looper::EVENT_OUTPUT,  Looper::EVENT_ERROR,
-        Looper::EVENT_HANGUP, Looper::EVENT_INVALID,
-};
-
-DisplayEventReceiver::Event buildDisplayEvent(FuzzedDataProvider* fdp, uint32_t type,
-                                              DisplayEventReceiver::Event event) {
-    switch (type) {
-        case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: {
-            event.vsync.count = fdp->ConsumeIntegral<uint32_t>();
-            event.vsync.vsyncData.frameInterval = fdp->ConsumeIntegral<uint64_t>();
-            event.vsync.vsyncData.preferredFrameTimelineIndex = fdp->ConsumeIntegral<uint32_t>();
-            for (size_t idx = 0; idx < gui::VsyncEventData::kFrameTimelinesCapacity; ++idx) {
-                event.vsync.vsyncData.frameTimelines[idx].vsyncId = fdp->ConsumeIntegral<int64_t>();
-                event.vsync.vsyncData.frameTimelines[idx].deadlineTimestamp =
-                        fdp->ConsumeIntegral<uint64_t>();
-                event.vsync.vsyncData.frameTimelines[idx].expectedPresentationTime =
-                        fdp->ConsumeIntegral<uint64_t>();
-            }
-            break;
-
-        }
-        case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: {
-            event.hotplug =
-                    DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/,
-                                                         fdp->ConsumeIntegral<
-                                                                 int32_t>() /*connectionError*/};
-            break;
-        }
-        case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: {
-            event.modeChange =
-                    DisplayEventReceiver::Event::ModeChange{fdp->ConsumeIntegral<int32_t>(),
-                                                            fdp->ConsumeIntegral<int64_t>()};
-            break;
-        }
-        case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
-        case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH: {
-            event.frameRateOverride =
-                    DisplayEventReceiver::Event::FrameRateOverride{fdp->ConsumeIntegral<uint32_t>(),
-                                                                   fdp->ConsumeFloatingPoint<
-                                                                           float>()};
-            break;
-        }
-    }
-    return event;
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    FuzzedDataProvider fdp(data, size);
-    sp<Looper> looper;
-    sp<FakeDisplayEventDispatcher> dispatcher(
-            new FakeDisplayEventDispatcher(looper, fdp.PickValueInArray(kVsyncSource),
-                                           fdp.PickValueInArray(kEventRegistration)));
-
-    dispatcher->initialize();
-    DisplayEventReceiver::Event event;
-    uint32_t type = fdp.PickValueInArray(kDisplayEvent);
-    PhysicalDisplayId displayId;
-    event.header =
-            DisplayEventReceiver::Event::Header{type, displayId, fdp.ConsumeIntegral<int64_t>()};
-    event = buildDisplayEvent(&fdp, type, event);
-
-    dispatcher->injectEvent(event);
-    dispatcher->handleEvent(0, fdp.PickValueInArray(kEvents), nullptr);
-    return 0;
-}
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
deleted file mode 100644
index 9933680..0000000
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ /dev/null
@@ -1,329 +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.
- */
-#include <android/gui/BnRegionSamplingListener.h>
-#include <android/gui/BnSurfaceComposer.h>
-#include <android/gui/BnSurfaceComposerClient.h>
-#include <android/gui/IDisplayEventConnection.h>
-#include <android/gui/ISurfaceComposerClient.h>
-#include <fuzzer/FuzzedDataProvider.h>
-#include <gmock/gmock.h>
-#include <gui/BLASTBufferQueue.h>
-#include <gui/DisplayEventDispatcher.h>
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/LayerDebugInfo.h>
-#include <gui/LayerState.h>
-#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
-#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
-#include <ui/fuzzer/FuzzableDataspaces.h>
-
-namespace android {
-
-constexpr uint32_t kOrientation[] = {
-        ui::Transform::ROT_0,  ui::Transform::FLIP_H,  ui::Transform::FLIP_V,
-        ui::Transform::ROT_90, ui::Transform::ROT_180, ui::Transform::ROT_270,
-};
-
-Rect getRect(FuzzedDataProvider* fdp) {
-    const int32_t left = fdp->ConsumeIntegral<int32_t>();
-    const int32_t top = fdp->ConsumeIntegral<int32_t>();
-    const int32_t right = fdp->ConsumeIntegral<int32_t>();
-    const int32_t bottom = fdp->ConsumeIntegral<int32_t>();
-    return Rect(left, top, right, bottom);
-}
-
-gui::DisplayBrightness getBrightness(FuzzedDataProvider* fdp) {
-    static constexpr float kMinBrightness = 0;
-    static constexpr float kMaxBrightness = 1;
-    gui::DisplayBrightness brightness;
-    brightness.sdrWhitePoint =
-            fdp->ConsumeFloatingPointInRange<float>(kMinBrightness, kMaxBrightness);
-    brightness.sdrWhitePointNits =
-            fdp->ConsumeFloatingPointInRange<float>(kMinBrightness, kMaxBrightness);
-    brightness.displayBrightness =
-            fdp->ConsumeFloatingPointInRange<float>(kMinBrightness, kMaxBrightness);
-    brightness.displayBrightnessNits =
-            fdp->ConsumeFloatingPointInRange<float>(kMinBrightness, kMaxBrightness);
-    return brightness;
-}
-
-class FakeBnSurfaceComposer : public gui::BnSurfaceComposer {
-public:
-    MOCK_METHOD(binder::Status, bootFinished, (), (override));
-    MOCK_METHOD(binder::Status, createDisplayEventConnection,
-                (gui::ISurfaceComposer::VsyncSource, gui::ISurfaceComposer::EventRegistration,
-                 const sp<IBinder>& /*layerHandle*/, sp<gui::IDisplayEventConnection>*),
-                (override));
-    MOCK_METHOD(binder::Status, createConnection, (sp<gui::ISurfaceComposerClient>*), (override));
-    MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, float, sp<IBinder>*),
-                (override));
-    MOCK_METHOD(binder::Status, destroyDisplay, (const sp<IBinder>&), (override));
-    MOCK_METHOD(binder::Status, getPhysicalDisplayIds, (std::vector<int64_t>*), (override));
-    MOCK_METHOD(binder::Status, getPhysicalDisplayToken, (int64_t, sp<IBinder>*), (override));
-    MOCK_METHOD(binder::Status, setPowerMode, (const sp<IBinder>&, int), (override));
-    MOCK_METHOD(binder::Status, getSupportedFrameTimestamps, (std::vector<FrameEvent>*),
-                (override));
-    MOCK_METHOD(binder::Status, getDisplayStats, (const sp<IBinder>&, gui::DisplayStatInfo*),
-                (override));
-    MOCK_METHOD(binder::Status, getDisplayState, (const sp<IBinder>&, gui::DisplayState*),
-                (override));
-    MOCK_METHOD(binder::Status, getStaticDisplayInfo, (int64_t, gui::StaticDisplayInfo*),
-                (override));
-    MOCK_METHOD(binder::Status, getDynamicDisplayInfoFromId, (int64_t, gui::DynamicDisplayInfo*),
-                (override));
-    MOCK_METHOD(binder::Status, getDynamicDisplayInfoFromToken,
-                (const sp<IBinder>&, gui::DynamicDisplayInfo*), (override));
-    MOCK_METHOD(binder::Status, getDisplayNativePrimaries,
-                (const sp<IBinder>&, gui::DisplayPrimaries*), (override));
-    MOCK_METHOD(binder::Status, setActiveColorMode, (const sp<IBinder>&, int), (override));
-    MOCK_METHOD(binder::Status, setBootDisplayMode, (const sp<IBinder>&, int), (override));
-    MOCK_METHOD(binder::Status, clearBootDisplayMode, (const sp<IBinder>&), (override));
-    MOCK_METHOD(binder::Status, getBootDisplayModeSupport, (bool*), (override));
-    MOCK_METHOD(binder::Status, getHdrConversionCapabilities,
-                (std::vector<gui::HdrConversionCapability>*), (override));
-    MOCK_METHOD(binder::Status, setHdrConversionStrategy,
-                (const gui::HdrConversionStrategy&, int32_t*), (override));
-    MOCK_METHOD(binder::Status, getHdrOutputConversionSupport, (bool*), (override));
-    MOCK_METHOD(binder::Status, setAutoLowLatencyMode, (const sp<IBinder>&, bool), (override));
-    MOCK_METHOD(binder::Status, setGameContentType, (const sp<IBinder>&, bool), (override));
-    MOCK_METHOD(binder::Status, captureDisplay,
-                (const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
-    MOCK_METHOD(binder::Status, captureDisplayById,
-                (int64_t, const gui::CaptureArgs&, const sp<IScreenCaptureListener>&), (override));
-    MOCK_METHOD(binder::Status, captureLayers,
-                (const LayerCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
-    MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override));
-    MOCK_METHOD(binder::Status, getAnimationFrameStats, (gui::FrameStats*), (override));
-    MOCK_METHOD(binder::Status, overrideHdrTypes, (const sp<IBinder>&, const std::vector<int32_t>&),
-                (override));
-    MOCK_METHOD(binder::Status, onPullAtom, (int32_t, gui::PullAtomData*), (override));
-    MOCK_METHOD(binder::Status, getLayerDebugInfo, (std::vector<gui::LayerDebugInfo>*), (override));
-    MOCK_METHOD(binder::Status, getCompositionPreference, (gui::CompositionPreference*),
-                (override));
-    MOCK_METHOD(binder::Status, getDisplayedContentSamplingAttributes,
-                (const sp<IBinder>&, gui::ContentSamplingAttributes*), (override));
-    MOCK_METHOD(binder::Status, setDisplayContentSamplingEnabled,
-                (const sp<IBinder>&, bool, int8_t, int64_t), (override));
-    MOCK_METHOD(binder::Status, getDisplayedContentSample,
-                (const sp<IBinder>&, int64_t, int64_t, gui::DisplayedFrameStats*), (override));
-    MOCK_METHOD(binder::Status, getProtectedContentSupport, (bool*), (override));
-    MOCK_METHOD(binder::Status, isWideColorDisplay, (const sp<IBinder>&, bool*), (override));
-    MOCK_METHOD(binder::Status, addRegionSamplingListener,
-                (const gui::ARect&, const sp<IBinder>&, const sp<gui::IRegionSamplingListener>&),
-                (override));
-    MOCK_METHOD(binder::Status, removeRegionSamplingListener,
-                (const sp<gui::IRegionSamplingListener>&), (override));
-    MOCK_METHOD(binder::Status, addFpsListener, (int32_t, const sp<gui::IFpsListener>&),
-                (override));
-    MOCK_METHOD(binder::Status, removeFpsListener, (const sp<gui::IFpsListener>&), (override));
-    MOCK_METHOD(binder::Status, addTunnelModeEnabledListener,
-                (const sp<gui::ITunnelModeEnabledListener>&), (override));
-    MOCK_METHOD(binder::Status, removeTunnelModeEnabledListener,
-                (const sp<gui::ITunnelModeEnabledListener>&), (override));
-    MOCK_METHOD(binder::Status, setDesiredDisplayModeSpecs,
-                (const sp<IBinder>&, const gui::DisplayModeSpecs&), (override));
-    MOCK_METHOD(binder::Status, getDesiredDisplayModeSpecs,
-                (const sp<IBinder>&, gui::DisplayModeSpecs*), (override));
-    MOCK_METHOD(binder::Status, getDisplayBrightnessSupport, (const sp<IBinder>&, bool*),
-                (override));
-    MOCK_METHOD(binder::Status, setDisplayBrightness,
-                (const sp<IBinder>&, const gui::DisplayBrightness&), (override));
-    MOCK_METHOD(binder::Status, addHdrLayerInfoListener,
-                (const sp<IBinder>&, const sp<gui::IHdrLayerInfoListener>&), (override));
-    MOCK_METHOD(binder::Status, removeHdrLayerInfoListener,
-                (const sp<IBinder>&, const sp<gui::IHdrLayerInfoListener>&), (override));
-    MOCK_METHOD(binder::Status, notifyPowerBoost, (int), (override));
-    MOCK_METHOD(binder::Status, setGlobalShadowSettings,
-                (const gui::Color&, const gui::Color&, float, float, float), (override));
-    MOCK_METHOD(binder::Status, getDisplayDecorationSupport,
-                (const sp<IBinder>&, std::optional<gui::DisplayDecorationSupport>*), (override));
-    MOCK_METHOD(binder::Status, setGameModeFrameRateOverride, (int32_t, float), (override));
-    MOCK_METHOD(binder::Status, setGameDefaultFrameRateOverride, (int32_t, float), (override));
-    MOCK_METHOD(binder::Status, enableRefreshRateOverlay, (bool), (override));
-    MOCK_METHOD(binder::Status, setDebugFlash, (int), (override));
-    MOCK_METHOD(binder::Status, scheduleComposite, (), (override));
-    MOCK_METHOD(binder::Status, scheduleCommit, (), (override));
-    MOCK_METHOD(binder::Status, forceClientComposition, (bool), (override));
-    MOCK_METHOD(binder::Status, updateSmallAreaDetection,
-                (const std::vector<int32_t>&, const std::vector<float>&), (override));
-    MOCK_METHOD(binder::Status, setSmallAreaDetectionThreshold, (int32_t, float), (override));
-    MOCK_METHOD(binder::Status, getGpuContextPriority, (int32_t*), (override));
-    MOCK_METHOD(binder::Status, getMaxAcquiredBufferCount, (int32_t*), (override));
-    MOCK_METHOD(binder::Status, addWindowInfosListener,
-                (const sp<gui::IWindowInfosListener>&, gui::WindowInfosListenerInfo*), (override));
-    MOCK_METHOD(binder::Status, removeWindowInfosListener, (const sp<gui::IWindowInfosListener>&),
-                (override));
-    MOCK_METHOD(binder::Status, getOverlaySupport, (gui::OverlayProperties*), (override));
-    MOCK_METHOD(binder::Status, getStalledTransactionInfo,
-                (int32_t, std::optional<gui::StalledTransactionInfo>*), (override));
-    MOCK_METHOD(binder::Status, getSchedulingPolicy, (gui::SchedulingPolicy*), (override));
-};
-
-class FakeBnSurfaceComposerClient : public gui::BnSurfaceComposerClient {
-public:
-    MOCK_METHOD(binder::Status, createSurface,
-                (const std::string& name, int32_t flags, const sp<IBinder>& parent,
-                 const gui::LayerMetadata& metadata, gui::CreateSurfaceResult* outResult),
-                (override));
-
-    MOCK_METHOD(binder::Status, clearLayerFrameStats, (const sp<IBinder>& handle), (override));
-
-    MOCK_METHOD(binder::Status, getLayerFrameStats,
-                (const sp<IBinder>& handle, gui::FrameStats* outStats), (override));
-
-    MOCK_METHOD(binder::Status, mirrorSurface,
-                (const sp<IBinder>& mirrorFromHandle, gui::CreateSurfaceResult* outResult),
-                (override));
-
-    MOCK_METHOD(binder::Status, mirrorDisplay,
-                (int64_t displayId, gui::CreateSurfaceResult* outResult), (override));
-
-    MOCK_METHOD(binder::Status, getSchedulingPolicy, (gui::SchedulingPolicy*), (override));
-};
-
-class FakeDisplayEventDispatcher : public DisplayEventDispatcher {
-public:
-    FakeDisplayEventDispatcher(const sp<Looper>& looper,
-                               gui::ISurfaceComposer::VsyncSource vsyncSource,
-                               gui::ISurfaceComposer::EventRegistration eventRegistration)
-          : DisplayEventDispatcher(looper, vsyncSource, eventRegistration){};
-
-    MOCK_METHOD4(dispatchVsync, void(nsecs_t, PhysicalDisplayId, uint32_t, VsyncEventData));
-    MOCK_METHOD3(dispatchHotplug, void(nsecs_t, PhysicalDisplayId, bool));
-    MOCK_METHOD2(dispatchHotplugConnectionError, void(nsecs_t, int32_t));
-    MOCK_METHOD4(dispatchModeChanged, void(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t));
-    MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId));
-    MOCK_METHOD3(dispatchFrameRateOverrides,
-                 void(nsecs_t, PhysicalDisplayId, std::vector<FrameRateOverride>));
-};
-
-} // namespace android
-
-namespace android::hardware {
-
-namespace graphics::bufferqueue::V1_0::utils {
-
-class FakeGraphicBufferProducerV1 : public HGraphicBufferProducer {
-public:
-    FakeGraphicBufferProducerV1() {
-        ON_CALL(*this, setMaxDequeuedBufferCount).WillByDefault([]() { return 0; });
-        ON_CALL(*this, setAsyncMode).WillByDefault([]() { return 0; });
-        ON_CALL(*this, detachBuffer).WillByDefault([]() { return 0; });
-        ON_CALL(*this, cancelBuffer).WillByDefault([]() { return 0; });
-        ON_CALL(*this, disconnect).WillByDefault([]() { return 0; });
-        ON_CALL(*this, setSidebandStream).WillByDefault([]() { return 0; });
-        ON_CALL(*this, allowAllocation).WillByDefault([]() { return 0; });
-        ON_CALL(*this, setGenerationNumber).WillByDefault([]() { return 0; });
-        ON_CALL(*this, setSharedBufferMode).WillByDefault([]() { return 0; });
-        ON_CALL(*this, setAutoRefresh).WillByDefault([]() { return 0; });
-        ON_CALL(*this, setDequeueTimeout).WillByDefault([]() { return 0; });
-        ON_CALL(*this, setLegacyBufferDrop).WillByDefault([]() { return 0; });
-    };
-    MOCK_METHOD2(requestBuffer, Return<void>(int, requestBuffer_cb));
-    MOCK_METHOD1(setMaxDequeuedBufferCount, Return<int32_t>(int32_t));
-    MOCK_METHOD1(setAsyncMode, Return<int32_t>(bool));
-    MOCK_METHOD6(dequeueBuffer,
-                 Return<void>(uint32_t, uint32_t, graphics::common::V1_0::PixelFormat, uint32_t,
-                              bool, dequeueBuffer_cb));
-    MOCK_METHOD1(detachBuffer, Return<int32_t>(int));
-    MOCK_METHOD1(detachNextBuffer, Return<void>(detachNextBuffer_cb));
-    MOCK_METHOD2(attachBuffer, Return<void>(const media::V1_0::AnwBuffer&, attachBuffer_cb));
-    MOCK_METHOD3(
-            queueBuffer,
-            Return<void>(
-                    int,
-                    const graphics::bufferqueue::V1_0::IGraphicBufferProducer::QueueBufferInput&,
-                    queueBuffer_cb));
-    MOCK_METHOD2(cancelBuffer, Return<int32_t>(int, const hidl_handle&));
-    MOCK_METHOD2(query, Return<void>(int32_t, query_cb));
-    MOCK_METHOD4(connect,
-                 Return<void>(const sp<graphics::bufferqueue::V1_0::IProducerListener>&, int32_t,
-                              bool, connect_cb));
-    MOCK_METHOD2(disconnect,
-                 Return<int32_t>(
-                         int, graphics::bufferqueue::V1_0::IGraphicBufferProducer::DisconnectMode));
-    MOCK_METHOD1(setSidebandStream, Return<int32_t>(const hidl_handle&));
-    MOCK_METHOD4(allocateBuffers,
-                 Return<void>(uint32_t, uint32_t, graphics::common::V1_0::PixelFormat, uint32_t));
-    MOCK_METHOD1(allowAllocation, Return<int32_t>(bool));
-    MOCK_METHOD1(setGenerationNumber, Return<int32_t>(uint32_t));
-    MOCK_METHOD1(getConsumerName, Return<void>(getConsumerName_cb));
-    MOCK_METHOD1(setSharedBufferMode, Return<int32_t>(bool));
-    MOCK_METHOD1(setAutoRefresh, Return<int32_t>(bool));
-    MOCK_METHOD1(setDequeueTimeout, Return<int32_t>(nsecs_t));
-    MOCK_METHOD1(setLegacyBufferDrop, Return<int32_t>(bool));
-    MOCK_METHOD1(getLastQueuedBuffer, Return<void>(getLastQueuedBuffer_cb));
-    MOCK_METHOD1(getFrameTimestamps, Return<void>(getFrameTimestamps_cb));
-    MOCK_METHOD1(getUniqueId, Return<void>(getUniqueId_cb));
-};
-
-}; // namespace graphics::bufferqueue::V1_0::utils
-
-namespace graphics::bufferqueue::V2_0::utils {
-
-class FakeGraphicBufferProducerV2 : public HGraphicBufferProducer {
-public:
-    FakeGraphicBufferProducerV2() {
-        ON_CALL(*this, setMaxDequeuedBufferCount).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, setAsyncMode).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, detachBuffer).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, cancelBuffer).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, disconnect).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, allocateBuffers).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, allowAllocation).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, setGenerationNumber).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, setDequeueTimeout).WillByDefault([]() { return Status::OK; });
-        ON_CALL(*this, getUniqueId).WillByDefault([]() { return 0; });
-    };
-    MOCK_METHOD2(requestBuffer, Return<void>(int, requestBuffer_cb));
-    MOCK_METHOD1(setMaxDequeuedBufferCount, Return<graphics::bufferqueue::V2_0::Status>(int));
-    MOCK_METHOD1(setAsyncMode, Return<graphics::bufferqueue::V2_0::Status>(bool));
-    MOCK_METHOD2(
-            dequeueBuffer,
-            Return<void>(
-                    const graphics::bufferqueue::V2_0::IGraphicBufferProducer::DequeueBufferInput&,
-                    dequeueBuffer_cb));
-    MOCK_METHOD1(detachBuffer, Return<graphics::bufferqueue::V2_0::Status>(int));
-    MOCK_METHOD1(detachNextBuffer, Return<void>(detachNextBuffer_cb));
-    MOCK_METHOD3(attachBuffer,
-                 Return<void>(const graphics::common::V1_2::HardwareBuffer&, uint32_t,
-                              attachBuffer_cb));
-    MOCK_METHOD3(
-            queueBuffer,
-            Return<void>(
-                    int,
-                    const graphics::bufferqueue::V2_0::IGraphicBufferProducer::QueueBufferInput&,
-                    queueBuffer_cb));
-    MOCK_METHOD2(cancelBuffer,
-                 Return<graphics::bufferqueue::V2_0::Status>(int, const hidl_handle&));
-    MOCK_METHOD2(query, Return<void>(int32_t, query_cb));
-    MOCK_METHOD4(connect,
-                 Return<void>(const sp<graphics::bufferqueue::V2_0::IProducerListener>&,
-                              graphics::bufferqueue::V2_0::ConnectionType, bool, connect_cb));
-    MOCK_METHOD1(disconnect,
-                 Return<graphics::bufferqueue::V2_0::Status>(
-                         graphics::bufferqueue::V2_0::ConnectionType));
-    MOCK_METHOD4(allocateBuffers,
-                 Return<graphics::bufferqueue::V2_0::Status>(uint32_t, uint32_t, uint32_t,
-                                                             uint64_t));
-    MOCK_METHOD1(allowAllocation, Return<graphics::bufferqueue::V2_0::Status>(bool));
-    MOCK_METHOD1(setGenerationNumber, Return<graphics::bufferqueue::V2_0::Status>(uint32_t));
-    MOCK_METHOD1(getConsumerName, Return<void>(getConsumerName_cb));
-    MOCK_METHOD1(setDequeueTimeout, Return<graphics::bufferqueue::V2_0::Status>(int64_t));
-    MOCK_METHOD0(getUniqueId, Return<uint64_t>());
-};
-
-}; // namespace graphics::bufferqueue::V2_0::utils
-}; // namespace android::hardware
diff --git a/libs/gui/fuzzer/libgui_parcelable_fuzzer.cpp b/libs/gui/fuzzer/libgui_parcelable_fuzzer.cpp
deleted file mode 100644
index 9f0f6ca..0000000
--- a/libs/gui/fuzzer/libgui_parcelable_fuzzer.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <gui/BufferQueueConsumer.h>
-#include <gui/BufferQueueCore.h>
-#include <gui/BufferQueueProducer.h>
-#include <gui/LayerMetadata.h>
-#include <gui/OccupancyTracker.h>
-#include <gui/StreamSplitter.h>
-#include <gui/Surface.h>
-#include <gui/SurfaceControl.h>
-#include <gui/view/Surface.h>
-#include <libgui_fuzzer_utils.h>
-#include "android/view/LayerMetadataKey.h"
-
-using namespace android;
-
-constexpr int32_t kMaxBytes = 256;
-constexpr int32_t kMatrixSize = 4;
-constexpr int32_t kLayerMetadataKeyCount = 8;
-
-constexpr uint32_t kMetadataKey[] = {
-        (uint32_t)view::LayerMetadataKey::METADATA_OWNER_UID,
-        (uint32_t)view::LayerMetadataKey::METADATA_WINDOW_TYPE,
-        (uint32_t)view::LayerMetadataKey::METADATA_TASK_ID,
-        (uint32_t)view::LayerMetadataKey::METADATA_MOUSE_CURSOR,
-        (uint32_t)view::LayerMetadataKey::METADATA_ACCESSIBILITY_ID,
-        (uint32_t)view::LayerMetadataKey::METADATA_OWNER_PID,
-        (uint32_t)view::LayerMetadataKey::METADATA_DEQUEUE_TIME,
-        (uint32_t)view::LayerMetadataKey::METADATA_GAME_MODE,
-};
-
-class ParcelableFuzzer {
-public:
-    ParcelableFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    void invokeStreamSplitter();
-    void invokeOccupancyTracker();
-    void invokeLayerDebugInfo();
-    void invokeLayerMetadata();
-    void invokeViewSurface();
-
-    FuzzedDataProvider mFdp;
-};
-
-void ParcelableFuzzer::invokeViewSurface() {
-    view::Surface surface;
-    surface.name = String16((mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str());
-    Parcel parcel;
-    surface.writeToParcel(&parcel);
-    parcel.setDataPosition(0);
-    surface.readFromParcel(&parcel);
-    bool nameAlreadyWritten = mFdp.ConsumeBool();
-    surface.writeToParcel(&parcel, nameAlreadyWritten);
-    parcel.setDataPosition(0);
-    surface.readFromParcel(&parcel, mFdp.ConsumeBool());
-}
-
-void ParcelableFuzzer::invokeLayerMetadata() {
-    std::unordered_map<uint32_t, std::vector<uint8_t>> map;
-    for (size_t idx = 0; idx < kLayerMetadataKeyCount; ++idx) {
-        std::vector<uint8_t> data;
-        for (size_t idx1 = 0; idx1 < mFdp.ConsumeIntegral<uint32_t>(); ++idx1) {
-            data.push_back(mFdp.ConsumeIntegral<uint8_t>());
-        }
-        map[kMetadataKey[idx]] = data;
-    }
-    LayerMetadata metadata(map);
-    uint32_t key = mFdp.PickValueInArray(kMetadataKey);
-    metadata.setInt32(key, mFdp.ConsumeIntegral<int32_t>());
-    metadata.itemToString(key, (mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str());
-
-    Parcel parcel;
-    metadata.writeToParcel(&parcel);
-    parcel.setDataPosition(0);
-    metadata.readFromParcel(&parcel);
-}
-
-void ParcelableFuzzer::invokeLayerDebugInfo() {
-    gui::LayerDebugInfo info;
-    info.mName = mFdp.ConsumeRandomLengthString(kMaxBytes);
-    info.mParentName = mFdp.ConsumeRandomLengthString(kMaxBytes);
-    info.mType = mFdp.ConsumeRandomLengthString(kMaxBytes);
-    info.mLayerStack = mFdp.ConsumeIntegral<uint32_t>();
-    info.mX = mFdp.ConsumeFloatingPoint<float>();
-    info.mY = mFdp.ConsumeFloatingPoint<float>();
-    info.mZ = mFdp.ConsumeIntegral<uint32_t>();
-    info.mWidth = mFdp.ConsumeIntegral<int32_t>();
-    info.mHeight = mFdp.ConsumeIntegral<int32_t>();
-    info.mActiveBufferWidth = mFdp.ConsumeIntegral<int32_t>();
-    info.mActiveBufferHeight = mFdp.ConsumeIntegral<int32_t>();
-    info.mActiveBufferStride = mFdp.ConsumeIntegral<int32_t>();
-    info.mActiveBufferFormat = mFdp.ConsumeIntegral<int32_t>();
-    info.mNumQueuedFrames = mFdp.ConsumeIntegral<int32_t>();
-
-    info.mFlags = mFdp.ConsumeIntegral<uint32_t>();
-    info.mPixelFormat = mFdp.ConsumeIntegral<int32_t>();
-    info.mTransparentRegion = Region(getRect(&mFdp));
-    info.mVisibleRegion = Region(getRect(&mFdp));
-    info.mSurfaceDamageRegion = Region(getRect(&mFdp));
-    info.mCrop = getRect(&mFdp);
-    info.mDataSpace = static_cast<android_dataspace>(mFdp.PickValueInArray(kDataspaces));
-    info.mColor = half4(mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                        mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>());
-    for (size_t idx = 0; idx < kMatrixSize; ++idx) {
-        info.mMatrix[idx / 2][idx % 2] = mFdp.ConsumeFloatingPoint<float>();
-    }
-    info.mIsOpaque = mFdp.ConsumeBool();
-    info.mContentDirty = mFdp.ConsumeBool();
-    info.mStretchEffect.width = mFdp.ConsumeFloatingPoint<float>();
-    info.mStretchEffect.height = mFdp.ConsumeFloatingPoint<float>();
-    info.mStretchEffect.vectorX = mFdp.ConsumeFloatingPoint<float>();
-    info.mStretchEffect.vectorY = mFdp.ConsumeFloatingPoint<float>();
-    info.mStretchEffect.maxAmountX = mFdp.ConsumeFloatingPoint<float>();
-    info.mStretchEffect.maxAmountY = mFdp.ConsumeFloatingPoint<float>();
-    info.mStretchEffect.mappedChildBounds =
-            FloatRect(mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                      mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>());
-
-    Parcel parcel;
-    info.writeToParcel(&parcel);
-    parcel.setDataPosition(0);
-    info.readFromParcel(&parcel);
-}
-
-void ParcelableFuzzer::invokeOccupancyTracker() {
-    nsecs_t totalTime = mFdp.ConsumeIntegral<uint32_t>();
-    size_t numFrames = mFdp.ConsumeIntegral<size_t>();
-    float occupancyAverage = mFdp.ConsumeFloatingPoint<float>();
-    OccupancyTracker::Segment segment(totalTime, numFrames, occupancyAverage,
-                                      mFdp.ConsumeBool() /*usedThirdBuffer*/);
-    Parcel parcel;
-    segment.writeToParcel(&parcel);
-    parcel.setDataPosition(0);
-    segment.readFromParcel(&parcel);
-}
-
-void ParcelableFuzzer::invokeStreamSplitter() {
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
-    sp<StreamSplitter> splitter;
-    StreamSplitter::createSplitter(consumer, &splitter);
-    splitter->addOutput(producer);
-    std::string name = mFdp.ConsumeRandomLengthString(kMaxBytes);
-    splitter->setName(String8(name.c_str()));
-}
-
-void ParcelableFuzzer::process() {
-    invokeStreamSplitter();
-    invokeOccupancyTracker();
-    invokeLayerDebugInfo();
-    invokeLayerMetadata();
-    invokeViewSurface();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    ParcelableFuzzer libGuiFuzzer(data, size);
-    libGuiFuzzer.process();
-    return 0;
-}
diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
deleted file mode 100644
index 4daa3be..0000000
--- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <aidl/android/hardware/power/Boost.h>
-#include <fuzzbinder/libbinder_driver.h>
-#include <gui/Surface.h>
-#include <gui/SurfaceComposerClient.h>
-#include <libgui_fuzzer_utils.h>
-#include "android-base/stringprintf.h"
-
-using namespace android;
-
-constexpr int32_t kRandomStringMaxBytes = 256;
-
-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};
-
-constexpr aidl::android::hardware::power::Boost kBoost[] = {
-        aidl::android::hardware::power::Boost::INTERACTION,
-        aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT,
-        aidl::android::hardware::power::Boost::ML_ACC,
-        aidl::android::hardware::power::Boost::AUDIO_LAUNCH,
-        aidl::android::hardware::power::Boost::CAMERA_LAUNCH,
-        aidl::android::hardware::power::Boost::CAMERA_SHOT,
-};
-
-constexpr gui::TouchOcclusionMode kMode[] = {
-        gui::TouchOcclusionMode::BLOCK_UNTRUSTED,
-        gui::TouchOcclusionMode::USE_OPACITY,
-        gui::TouchOcclusionMode::ALLOW,
-};
-
-constexpr gui::WindowInfo::Flag kFlags[] = {
-        gui::WindowInfo::Flag::ALLOW_LOCK_WHILE_SCREEN_ON,
-        gui::WindowInfo::Flag::DIM_BEHIND,
-        gui::WindowInfo::Flag::BLUR_BEHIND,
-        gui::WindowInfo::Flag::NOT_FOCUSABLE,
-        gui::WindowInfo::Flag::NOT_TOUCHABLE,
-        gui::WindowInfo::Flag::NOT_TOUCH_MODAL,
-        gui::WindowInfo::Flag::TOUCHABLE_WHEN_WAKING,
-        gui::WindowInfo::Flag::KEEP_SCREEN_ON,
-        gui::WindowInfo::Flag::LAYOUT_IN_SCREEN,
-        gui::WindowInfo::Flag::LAYOUT_NO_LIMITS,
-        gui::WindowInfo::Flag::FULLSCREEN,
-        gui::WindowInfo::Flag::FORCE_NOT_FULLSCREEN,
-        gui::WindowInfo::Flag::DITHER,
-        gui::WindowInfo::Flag::SECURE,
-        gui::WindowInfo::Flag::SCALED,
-        gui::WindowInfo::Flag::IGNORE_CHEEK_PRESSES,
-        gui::WindowInfo::Flag::LAYOUT_INSET_DECOR,
-        gui::WindowInfo::Flag::ALT_FOCUSABLE_IM,
-        gui::WindowInfo::Flag::WATCH_OUTSIDE_TOUCH,
-        gui::WindowInfo::Flag::SHOW_WHEN_LOCKED,
-        gui::WindowInfo::Flag::SHOW_WALLPAPER,
-        gui::WindowInfo::Flag::TURN_SCREEN_ON,
-        gui::WindowInfo::Flag::DISMISS_KEYGUARD,
-        gui::WindowInfo::Flag::SPLIT_TOUCH,
-        gui::WindowInfo::Flag::HARDWARE_ACCELERATED,
-        gui::WindowInfo::Flag::LAYOUT_IN_OVERSCAN,
-        gui::WindowInfo::Flag::TRANSLUCENT_STATUS,
-        gui::WindowInfo::Flag::TRANSLUCENT_NAVIGATION,
-        gui::WindowInfo::Flag::LOCAL_FOCUS_MODE,
-        gui::WindowInfo::Flag::SLIPPERY,
-        gui::WindowInfo::Flag::LAYOUT_ATTACHED_IN_DECOR,
-        gui::WindowInfo::Flag::DRAWS_SYSTEM_BAR_BACKGROUNDS,
-};
-
-constexpr gui::WindowInfo::Type kType[] = {
-        gui::WindowInfo::Type::UNKNOWN,
-        gui::WindowInfo::Type::FIRST_APPLICATION_WINDOW,
-        gui::WindowInfo::Type::BASE_APPLICATION,
-        gui::WindowInfo::Type::APPLICATION,
-        gui::WindowInfo::Type::APPLICATION_STARTING,
-        gui::WindowInfo::Type::LAST_APPLICATION_WINDOW,
-        gui::WindowInfo::Type::FIRST_SUB_WINDOW,
-        gui::WindowInfo::Type::APPLICATION_PANEL,
-        gui::WindowInfo::Type::APPLICATION_MEDIA,
-        gui::WindowInfo::Type::APPLICATION_SUB_PANEL,
-        gui::WindowInfo::Type::APPLICATION_ATTACHED_DIALOG,
-        gui::WindowInfo::Type::APPLICATION_MEDIA_OVERLAY,
-};
-
-constexpr gui::WindowInfo::InputConfig kFeatures[] = {
-        gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL,
-        gui::WindowInfo::InputConfig::DISABLE_USER_ACTIVITY,
-        gui::WindowInfo::InputConfig::DROP_INPUT,
-        gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED,
-        gui::WindowInfo::InputConfig::SPY,
-        gui::WindowInfo::InputConfig::INTERCEPTS_STYLUS,
-};
-
-class SurfaceComposerClientFuzzer {
-public:
-    SurfaceComposerClientFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    void invokeSurfaceComposerClient();
-    void invokeSurfaceComposerClientBinder();
-    void invokeSurfaceComposerTransaction();
-    void getWindowInfo(gui::WindowInfo*);
-    sp<SurfaceControl> makeSurfaceControl();
-    BlurRegion getBlurRegion();
-    void fuzzOnPullAtom();
-    gui::DisplayModeSpecs getDisplayModeSpecs();
-
-    FuzzedDataProvider mFdp;
-};
-
-gui::DisplayModeSpecs SurfaceComposerClientFuzzer::getDisplayModeSpecs() {
-    const auto getRefreshRateRange = [&] {
-        gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range;
-        range.min = mFdp.ConsumeFloatingPoint<float>();
-        range.max = mFdp.ConsumeFloatingPoint<float>();
-        return range;
-    };
-
-    const auto getRefreshRateRanges = [&] {
-        gui::DisplayModeSpecs::RefreshRateRanges ranges;
-        ranges.physical = getRefreshRateRange();
-        ranges.render = getRefreshRateRange();
-        return ranges;
-    };
-
-    String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
-    sp<IBinder> displayToken =
-            SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/);
-    gui::DisplayModeSpecs specs;
-    specs.defaultMode = mFdp.ConsumeIntegral<int32_t>();
-    specs.allowGroupSwitching = mFdp.ConsumeBool();
-    specs.primaryRanges = getRefreshRateRanges();
-    specs.appRequestRanges = getRefreshRateRanges();
-    return specs;
-}
-
-BlurRegion SurfaceComposerClientFuzzer::getBlurRegion() {
-    int32_t left = mFdp.ConsumeIntegral<int32_t>();
-    int32_t right = mFdp.ConsumeIntegral<int32_t>();
-    int32_t top = mFdp.ConsumeIntegral<int32_t>();
-    int32_t bottom = mFdp.ConsumeIntegral<int32_t>();
-    uint32_t blurRadius = mFdp.ConsumeIntegral<uint32_t>();
-    float alpha = mFdp.ConsumeFloatingPoint<float>();
-    float cornerRadiusTL = mFdp.ConsumeFloatingPoint<float>();
-    float cornerRadiusTR = mFdp.ConsumeFloatingPoint<float>();
-    float cornerRadiusBL = mFdp.ConsumeFloatingPoint<float>();
-    float cornerRadiusBR = mFdp.ConsumeFloatingPoint<float>();
-    return BlurRegion{blurRadius,     cornerRadiusTL, cornerRadiusTR, cornerRadiusBL,
-                      cornerRadiusBR, alpha,          left,           top,
-                      right,          bottom};
-}
-
-void SurfaceComposerClientFuzzer::getWindowInfo(gui::WindowInfo* windowInfo) {
-    windowInfo->id = mFdp.ConsumeIntegral<int32_t>();
-    windowInfo->name = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes);
-    windowInfo->layoutParamsFlags = mFdp.PickValueInArray(kFlags);
-    windowInfo->layoutParamsType = mFdp.PickValueInArray(kType);
-    windowInfo->frame = Rect(mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>(),
-                             mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>());
-    windowInfo->surfaceInset = mFdp.ConsumeIntegral<int32_t>();
-    windowInfo->alpha = mFdp.ConsumeFloatingPointInRange<float>(0, 1);
-    ui::Transform transform(mFdp.PickValueInArray(kOrientation));
-    windowInfo->transform = transform;
-    windowInfo->touchableRegion = Region(getRect(&mFdp));
-    windowInfo->replaceTouchableRegionWithCrop = mFdp.ConsumeBool();
-    windowInfo->touchOcclusionMode = mFdp.PickValueInArray(kMode);
-    windowInfo->ownerPid = gui::Pid{mFdp.ConsumeIntegral<pid_t>()};
-    windowInfo->ownerUid = gui::Uid{mFdp.ConsumeIntegral<uid_t>()};
-    windowInfo->packageName = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes);
-    windowInfo->inputConfig = mFdp.PickValueInArray(kFeatures);
-}
-
-sp<SurfaceControl> SurfaceComposerClientFuzzer::makeSurfaceControl() {
-    sp<IBinder> handle;
-    const sp<FakeBnSurfaceComposerClient> testClient(new FakeBnSurfaceComposerClient());
-    sp<SurfaceComposerClient> client = new SurfaceComposerClient(testClient);
-    sp<BnGraphicBufferProducer> producer;
-    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
-    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
-    uint32_t transformHint = mFdp.ConsumeIntegral<uint32_t>();
-    uint32_t flags = mFdp.ConsumeIntegral<uint32_t>();
-    int32_t format = mFdp.ConsumeIntegral<int32_t>();
-    int32_t layerId = mFdp.ConsumeIntegral<int32_t>();
-    std::string layerName = base::StringPrintf("#%d", layerId);
-    return new SurfaceControl(client, handle, layerId, layerName, width, height, format,
-                              transformHint, flags);
-}
-
-void SurfaceComposerClientFuzzer::invokeSurfaceComposerTransaction() {
-    sp<SurfaceControl> surface = makeSurfaceControl();
-
-    SurfaceComposerClient::Transaction transaction;
-    int32_t layer = mFdp.ConsumeIntegral<int32_t>();
-    transaction.setLayer(surface, layer);
-
-    sp<SurfaceControl> relativeSurface = makeSurfaceControl();
-    transaction.setRelativeLayer(surface, relativeSurface, layer);
-
-    Region transparentRegion(getRect(&mFdp));
-    transaction.setTransparentRegionHint(surface, transparentRegion);
-    transaction.setAlpha(surface, mFdp.ConsumeFloatingPoint<float>());
-
-    transaction.setCornerRadius(surface, mFdp.ConsumeFloatingPoint<float>());
-    transaction.setBackgroundBlurRadius(surface, mFdp.ConsumeFloatingPoint<float>());
-    std::vector<BlurRegion> regions;
-    uint32_t vectorSize = mFdp.ConsumeIntegralInRange<uint32_t>(0, 100);
-    regions.resize(vectorSize);
-    for (size_t idx = 0; idx < vectorSize; ++idx) {
-        regions.push_back(getBlurRegion());
-    }
-    transaction.setBlurRegions(surface, regions);
-
-    transaction.setLayerStack(surface, {mFdp.ConsumeIntegral<uint32_t>()});
-    half3 color = {mFdp.ConsumeIntegral<uint32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
-                   mFdp.ConsumeIntegral<uint32_t>()};
-    transaction.setColor(surface, color);
-    transaction.setBackgroundColor(surface, color, mFdp.ConsumeFloatingPoint<float>(),
-                                   mFdp.PickValueInArray(kDataspaces));
-
-    transaction.setApi(surface, mFdp.ConsumeIntegral<int32_t>());
-    transaction.setFrameRateSelectionPriority(surface, mFdp.ConsumeIntegral<int32_t>());
-    transaction.setColorSpaceAgnostic(surface, mFdp.ConsumeBool() /*agnostic*/);
-
-    gui::WindowInfo windowInfo;
-    getWindowInfo(&windowInfo);
-    transaction.setInputWindowInfo(surface, windowInfo);
-    Parcel windowParcel;
-    windowInfo.writeToParcel(&windowParcel);
-    windowParcel.setDataPosition(0);
-    windowInfo.readFromParcel(&windowParcel);
-
-    windowInfo.addTouchableRegion(getRect(&mFdp));
-    int32_t pointX = mFdp.ConsumeIntegral<int32_t>();
-    int32_t pointY = mFdp.ConsumeIntegral<int32_t>();
-    windowInfo.touchableRegionContainsPoint(pointX, pointY);
-    windowInfo.frameContainsPoint(pointX, pointY);
-
-    Parcel transactionParcel;
-    transaction.writeToParcel(&transactionParcel);
-    transactionParcel.setDataPosition(0);
-    transaction.readFromParcel(&transactionParcel);
-    SurfaceComposerClient::Transaction::createFromParcel(&transactionParcel);
-}
-
-void SurfaceComposerClientFuzzer::fuzzOnPullAtom() {
-    std::string outData;
-    bool success;
-    SurfaceComposerClient::onPullAtom(mFdp.ConsumeIntegral<int32_t>(), &outData, &success);
-}
-
-void SurfaceComposerClientFuzzer::invokeSurfaceComposerClient() {
-    String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
-    sp<IBinder> displayToken =
-            SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/);
-    SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, getDisplayModeSpecs());
-
-    ui::ColorMode colorMode = mFdp.PickValueInArray(kColormodes);
-    SurfaceComposerClient::setActiveColorMode(displayToken, colorMode);
-    SurfaceComposerClient::setAutoLowLatencyMode(displayToken, mFdp.ConsumeBool() /*on*/);
-    SurfaceComposerClient::setGameContentType(displayToken, mFdp.ConsumeBool() /*on*/);
-    SurfaceComposerClient::setDisplayPowerMode(displayToken, mFdp.ConsumeIntegral<int32_t>());
-    SurfaceComposerClient::doUncacheBufferTransaction(mFdp.ConsumeIntegral<uint64_t>());
-
-    SurfaceComposerClient::setDisplayBrightness(displayToken, getBrightness(&mFdp));
-    aidl::android::hardware::power::Boost boostId = mFdp.PickValueInArray(kBoost);
-    SurfaceComposerClient::notifyPowerBoost((int32_t)boostId);
-
-    String8 surfaceName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
-    sp<BBinder> handle(new BBinder());
-    sp<BnGraphicBufferProducer> producer;
-    sp<Surface> surfaceParent(
-            new Surface(producer, mFdp.ConsumeBool() /*controlledByApp*/, handle));
-
-    fuzzOnPullAtom();
-    SurfaceComposerClient::setDisplayContentSamplingEnabled(displayToken,
-                                                            mFdp.ConsumeBool() /*enable*/,
-                                                            mFdp.ConsumeIntegral<uint8_t>(),
-                                                            mFdp.ConsumeIntegral<uint64_t>());
-
-    sp<IBinder> stopLayerHandle;
-    sp<gui::IRegionSamplingListener> listener = sp<gui::IRegionSamplingListenerDefault>::make();
-    sp<gui::IRegionSamplingListenerDelegator> sampleListener =
-            new gui::IRegionSamplingListenerDelegator(listener);
-    SurfaceComposerClient::addRegionSamplingListener(getRect(&mFdp), stopLayerHandle,
-                                                     sampleListener);
-    sp<gui::IFpsListenerDefault> fpsListener;
-    SurfaceComposerClient::addFpsListener(mFdp.ConsumeIntegral<int32_t>(), fpsListener);
-}
-
-void SurfaceComposerClientFuzzer::invokeSurfaceComposerClientBinder() {
-    sp<FakeBnSurfaceComposerClient> client(new FakeBnSurfaceComposerClient());
-    fuzzService(client.get(), std::move(mFdp));
-}
-
-void SurfaceComposerClientFuzzer::process() {
-    invokeSurfaceComposerClient();
-    invokeSurfaceComposerTransaction();
-    invokeSurfaceComposerClientBinder();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    SurfaceComposerClientFuzzer surfaceComposerClientFuzzer(data, size);
-    surfaceComposerClientFuzzer.process();
-    return 0;
-}
diff --git a/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp
deleted file mode 100644
index 6d5427b..0000000
--- a/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp
+++ /dev/null
@@ -1,41 +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.
- */
-
-#include <fuzzbinder/libbinder_driver.h>
-#include <fuzzer/FuzzedDataProvider.h>
-#include <libgui_fuzzer_utils.h>
-
-using namespace android;
-
-class SurfaceComposerFuzzer {
-public:
-    SurfaceComposerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-void SurfaceComposerFuzzer::process() {
-    sp<FakeBnSurfaceComposer> composer(new FakeBnSurfaceComposer());
-    fuzzService(composer.get(), std::move(mFdp));
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    SurfaceComposerFuzzer surfaceComposerFuzzer(data, size);
-    surfaceComposerFuzzer.process();
-    return 0;
-}
diff --git a/libs/gui/include/gui/AdditionalOptions.h b/libs/gui/include/gui/AdditionalOptions.h
new file mode 100644
index 0000000..87cb913
--- /dev/null
+++ b/libs/gui/include/gui/AdditionalOptions.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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 <string>
+
+namespace android::gui {
+// Additional options to pass to AHardwareBuffer_allocateWithOptions.
+// See also allocator-v2's BufferDescriptorInfo.aidl
+struct AdditionalOptions {
+    std::string name;
+    int64_t value;
+
+    bool operator==(const AdditionalOptions& other) const {
+        return value == other.value && name == other.name;
+    }
+};
+} // namespace android::gui
diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h
index 22c2be7..bb52c8e 100644
--- a/libs/gui/include/gui/BufferQueueCore.h
+++ b/libs/gui/include/gui/BufferQueueCore.h
@@ -17,6 +17,9 @@
 #ifndef ANDROID_GUI_BUFFERQUEUECORE_H
 #define ANDROID_GUI_BUFFERQUEUECORE_H
 
+#include <com_android_graphics_libgui_flags.h>
+
+#include <gui/AdditionalOptions.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/BufferSlot.h>
@@ -357,6 +360,14 @@
     // This allows the consumer to acquire an additional buffer if that buffer is not droppable and
     // will eventually be released or acquired by the consumer.
     bool mAllowExtraAcquire = false;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    // Additional options to pass when allocating GraphicBuffers.
+    // GenerationID changes when the options change, indicating reallocation is required
+    uint32_t mAdditionalOptionsGenerationId = 0;
+    std::vector<gui::AdditionalOptions> mAdditionalOptions;
+#endif
+
 }; // class BufferQueueCore
 
 } // namespace android
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index de47483..37a9607 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 #define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 
+#include <gui/AdditionalOptions.h>
 #include <gui/BufferQueueDefs.h>
 
 #include <gui/IGraphicBufferProducer.h>
@@ -208,6 +209,10 @@
                           int8_t changeFrameRateStrategy) override;
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) override;
+#endif
+
 protected:
     // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
     // total maximum buffer count for the buffer queue (dequeued AND acquired)
diff --git a/libs/gui/include/gui/BufferSlot.h b/libs/gui/include/gui/BufferSlot.h
index 57704b1..5b32710 100644
--- a/libs/gui/include/gui/BufferSlot.h
+++ b/libs/gui/include/gui/BufferSlot.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_GUI_BUFFERSLOT_H
 #define ANDROID_GUI_BUFFERSLOT_H
 
+#include <com_android_graphics_libgui_flags.h>
+
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
 
@@ -230,6 +232,11 @@
     // producer. If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when
     // dequeued to prevent the producer from using a stale cached buffer.
     bool mNeedsReallocation;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    // The generation id of the additional options that mGraphicBuffer was allocated with
+    uint32_t mAdditionalOptionsGenerationId = 0;
+#endif
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 9fef512..2e5aa4a 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -28,12 +28,18 @@
 namespace android {
 using gui::VsyncEventData;
 
+enum CallbackType : int8_t {
+    CALLBACK_INPUT,
+    CALLBACK_ANIMATION,
+};
+
 struct FrameCallback {
     AChoreographer_frameCallback callback;
     AChoreographer_frameCallback64 callback64;
     AChoreographer_vsyncCallback vsyncCallback;
     void* data;
     nsecs_t dueTime;
+    CallbackType callbackType;
 
     inline bool operator<(const FrameCallback& rhs) const {
         // Note that this is intentionally flipped because we want callbacks due sooner to be at
@@ -78,7 +84,7 @@
     void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                   AChoreographer_frameCallback64 cb64,
                                   AChoreographer_vsyncCallback vsyncCallback, void* data,
-                                  nsecs_t delay);
+                                  nsecs_t delay, CallbackType callbackType);
     void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data)
             EXCLUDES(gChoreographers.lock);
     void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data);
@@ -103,12 +109,15 @@
     virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
     int64_t getFrameInterval() const;
     bool inCallback() const;
+    const sp<Looper> getLooper();
 
 private:
     Choreographer(const Choreographer&) = delete;
 
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
                        VsyncEventData vsyncEventData) override;
+    void dispatchCallbacks(const std::vector<FrameCallback>&, VsyncEventData vsyncEventData,
+                           nsecs_t timestamp);
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
     void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
@@ -116,6 +125,8 @@
     void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                     std::vector<FrameRateOverride> overrides) override;
+    void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                   int32_t maxLevel) override;
 
     void scheduleCallbacks();
 
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index fe2dd20..82cd50c 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -65,6 +65,9 @@
     virtual void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                             std::vector<FrameRateOverride> overrides) = 0;
 
+    virtual void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                           int32_t maxLevel) = 0;
+
     bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
                               uint32_t* outCount, VsyncEventData* outVsyncEventData);
 
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 79582ce..4dbf9e1 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -58,7 +58,6 @@
 // ----------------------------------------------------------------------------
 class DisplayEventReceiver {
 public:
-
     enum {
         DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
         DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
@@ -66,6 +65,7 @@
         DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
+        DISPLAY_EVENT_HDCP_LEVELS_CHANGE = fourcc('h', 'd', 'c', 'p'),
     };
 
     struct Event {
@@ -101,14 +101,25 @@
             float frameRateHz __attribute__((aligned(8)));
         };
 
+        /*
+         * The values are defined in aidl:
+         * hardware/interfaces/drm/aidl/android/hardware/drm/HdcpLevel.aidl
+         */
+        struct HdcpLevelsChange {
+            int32_t connectedLevel;
+            int32_t maxLevel;
+        };
+
         Header header;
         union {
             VSync vsync;
             Hotplug hotplug;
             ModeChange modeChange;
             FrameRateOverride frameRateOverride;
+            HdcpLevelsChange hdcpLevelsChange;
         };
     };
+    static_assert(sizeof(Event) == 216);
 
 public:
     /*
diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h
index 42b62c7..7094658 100644
--- a/libs/gui/include/gui/DisplayInfo.h
+++ b/libs/gui/include/gui/DisplayInfo.h
@@ -18,7 +18,7 @@
 
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
-#include <gui/constants.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Transform.h>
 
 namespace android::gui {
@@ -29,7 +29,7 @@
  * This should only be used by InputFlinger to support raw coordinates in logical display space.
  */
 struct DisplayInfo : public Parcelable {
-    int32_t displayId = ADISPLAY_ID_NONE;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
 
     // Logical display dimensions.
     int32_t logicalWidth = 0;
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 7639e70..8fca946 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -31,6 +31,7 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include <gui/AdditionalOptions.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/HdrMetadata.h>
 
@@ -684,6 +685,10 @@
                                   int8_t changeFrameRateStrategy);
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    virtual status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options);
+#endif
+
     struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
         RequestBufferOutput() = default;
 
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index a836f46..eb4a802 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -74,7 +74,6 @@
 
 struct DisplayCaptureArgs;
 struct LayerCaptureArgs;
-class LayerDebugInfo;
 
 } // namespace gui
 
@@ -131,8 +130,8 @@
         CREATE_CONNECTION,               // Deprecated. Autogenerated by .aidl now.
         GET_STATIC_DISPLAY_INFO,         // Deprecated. Autogenerated by .aidl now.
         CREATE_DISPLAY_EVENT_CONNECTION, // Deprecated. Autogenerated by .aidl now.
-        CREATE_DISPLAY,                  // Deprecated. Autogenerated by .aidl now.
-        DESTROY_DISPLAY,                 // Deprecated. Autogenerated by .aidl now.
+        CREATE_VIRTUAL_DISPLAY,          // Deprecated. Autogenerated by .aidl now.
+        DESTROY_VIRTUAL_DISPLAY,         // Deprecated. Autogenerated by .aidl now.
         GET_PHYSICAL_DISPLAY_TOKEN,      // Deprecated. Autogenerated by .aidl now.
         SET_TRANSACTION_STATE,
         AUTHENTICATE_SURFACE,           // Deprecated. Autogenerated by .aidl now.
diff --git a/libs/gui/include/gui/InputTransferToken.h b/libs/gui/include/gui/InputTransferToken.h
new file mode 100644
index 0000000..6530b50
--- /dev/null
+++ b/libs/gui/include/gui/InputTransferToken.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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 <binder/Binder.h>
+#include <binder/IBinder.h>
+#include <binder/Parcel.h>
+#include <private/gui/ParcelUtils.h>
+#include <utils/Errors.h>
+
+namespace android {
+struct InputTransferToken : public RefBase, Parcelable {
+public:
+    InputTransferToken() { mToken = new BBinder(); }
+
+    InputTransferToken(const sp<IBinder>& token) { mToken = token; }
+
+    status_t writeToParcel(Parcel* parcel) const override {
+        SAFE_PARCEL(parcel->writeStrongBinder, mToken);
+        return NO_ERROR;
+    }
+
+    status_t readFromParcel(const Parcel* parcel) {
+        SAFE_PARCEL(parcel->readStrongBinder, &mToken);
+        return NO_ERROR;
+    };
+
+    sp<IBinder> mToken;
+};
+
+static inline bool operator==(const sp<InputTransferToken>& token1,
+                              const sp<InputTransferToken>& token2) {
+    if (token1.get() == token2.get()) {
+        return true;
+    }
+    return token1->mToken == token2->mToken;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/LayerDebugInfo.h b/libs/gui/include/gui/LayerDebugInfo.h
deleted file mode 100644
index dbb80e5..0000000
--- a/libs/gui/include/gui/LayerDebugInfo.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 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 <binder/Parcelable.h>
-
-#include <ui/PixelFormat.h>
-#include <ui/Region.h>
-#include <ui/StretchEffect.h>
-
-#include <string>
-#include <math/vec4.h>
-
-namespace android::gui {
-
-/* Class for transporting debug info from SurfaceFlinger to authorized
- * recipients.  The class is intended to be a data container. There are
- * no getters or setters.
- */
-class LayerDebugInfo : public Parcelable {
-public:
-    LayerDebugInfo() = default;
-    LayerDebugInfo(const LayerDebugInfo&) = default;
-    virtual ~LayerDebugInfo() = default;
-
-    virtual status_t writeToParcel(Parcel* parcel) const;
-    virtual status_t readFromParcel(const Parcel* parcel);
-
-    std::string mName = std::string("NOT FILLED");
-    std::string mParentName = std::string("NOT FILLED");
-    std::string mType = std::string("NOT FILLED");
-    Region mTransparentRegion = Region::INVALID_REGION;
-    Region mVisibleRegion = Region::INVALID_REGION;
-    Region mSurfaceDamageRegion = Region::INVALID_REGION;
-    uint32_t mLayerStack = 0;
-    float mX = 0.f;
-    float mY = 0.f;
-    uint32_t mZ = 0 ;
-    int32_t mWidth = -1;
-    int32_t mHeight = -1;
-    android::Rect mCrop = android::Rect::INVALID_RECT;
-    half4 mColor = half4(1.0_hf, 1.0_hf, 1.0_hf, 0.0_hf);
-    uint32_t mFlags = 0;
-    PixelFormat mPixelFormat = PIXEL_FORMAT_NONE;
-    android_dataspace mDataSpace = HAL_DATASPACE_UNKNOWN;
-    // Row-major transform matrix (SurfaceControl::setMatrix())
-    float mMatrix[2][2] = {{0.f, 0.f}, {0.f, 0.f}};
-    int32_t mActiveBufferWidth = -1;
-    int32_t mActiveBufferHeight = -1;
-    int32_t mActiveBufferStride = 0;
-    PixelFormat mActiveBufferFormat = PIXEL_FORMAT_NONE;
-    int32_t mNumQueuedFrames = -1;
-    bool mIsOpaque = false;
-    bool mContentDirty = false;
-    StretchEffect mStretchEffect = {};
-};
-
-std::string to_string(const LayerDebugInfo& info);
-
-} // namespace android::gui
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index e1dc791..82889ef 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -30,6 +30,7 @@
 
 #include <android/gui/DropInputMode.h>
 #include <android/gui/FocusRequest.h>
+#include <android/gui/TrustedOverlay.h>
 
 #include <ftl/flags.h>
 #include <gui/DisplayCaptureArgs.h>
@@ -161,6 +162,9 @@
         // See SurfaceView scaling behavior for more details.
         eIgnoreDestinationFrame = 0x400,
         eLayerIsRefreshRateIndicator = 0x800, // REFRESH_RATE_INDICATOR
+        // Sets a property on this layer indicating that its visible region should be considered
+        // when computing TrustedPresentation Thresholds.
+        eCanOccludePresentation = 0x1000,
     };
 
     enum {
@@ -176,7 +180,6 @@
         eCachingHintChanged = 0x00000200,
         eDimmingEnabledChanged = 0x00000400,
         eShadowRadiusChanged = 0x00000800,
-        eRenderBorderChanged = 0x00001000,
         eBufferCropChanged = 0x00002000,
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
@@ -206,7 +209,7 @@
         eBackgroundBlurRadiusChanged = 0x80'00000000,
         eProducerDisconnect = 0x100'00000000,
         eFixedTransformHintChanged = 0x200'00000000,
-        /* unused 0x400'00000000, */
+        eDesiredHdrHeadroomChanged = 0x400'00000000,
         eBlurRegionsChanged = 0x800'00000000,
         eAutoRefreshChanged = 0x1000'00000000,
         eStretchChanged = 0x2000'00000000,
@@ -245,7 +248,8 @@
             layer_state_t::eSidebandStreamChanged | layer_state_t::eSurfaceDamageRegionChanged |
             layer_state_t::eTransformToDisplayInverseChanged |
             layer_state_t::eTransparentRegionChanged |
-            layer_state_t::eExtendedRangeBrightnessChanged;
+            layer_state_t::eExtendedRangeBrightnessChanged |
+            layer_state_t::eDesiredHdrHeadroomChanged;
 
     // Content updates.
     static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES |
@@ -254,8 +258,8 @@
             layer_state_t::eBlurRegionsChanged | layer_state_t::eColorChanged |
             layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
             layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged |
-            layer_state_t::eHdrMetadataChanged | layer_state_t::eRenderBorderChanged |
-            layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged;
+            layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged |
+            layer_state_t::eStretchChanged;
 
     // Changes which invalidates the layer's visible region in CE.
     static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES |
@@ -382,12 +386,7 @@
 
     // An inherited state that indicates that this surface control and its children
     // should be trusted for input occlusion detection purposes
-    bool isTrustedOverlay;
-
-    // Flag to indicate if border needs to be enabled on the layer
-    bool borderEnabled;
-    float borderWidth;
-    half4 borderColor;
+    gui::TrustedOverlay trustedOverlay;
 
     // Stretch effect to be applied to this layer
     StretchEffect stretchEffect;
@@ -416,26 +415,36 @@
 };
 
 struct DisplayState {
-    enum {
+    enum : uint32_t {
         eSurfaceChanged = 0x01,
         eLayerStackChanged = 0x02,
         eDisplayProjectionChanged = 0x04,
         eDisplaySizeChanged = 0x08,
-        eFlagsChanged = 0x10
+        eFlagsChanged = 0x10,
+
+        eAllChanged = ~0u
     };
 
+    // Not for direct use. Prefer constructor below for new displays.
     DisplayState();
+
+    DisplayState(sp<IBinder> token, ui::LayerStack layerStack)
+          : what(eAllChanged),
+            token(std::move(token)),
+            layerStack(layerStack),
+            layerStackSpaceRect(Rect::INVALID_RECT),
+            orientedDisplaySpaceRect(Rect::INVALID_RECT) {}
+
     void merge(const DisplayState& other);
     void sanitize(int32_t permissions);
 
     uint32_t what = 0;
     uint32_t flags = 0;
     sp<IBinder> token;
-    sp<IGraphicBufferProducer> surface;
 
     ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK;
 
-    // These states define how layers are projected onto the physical display.
+    // These states define how layers are projected onto the physical or virtual display.
     //
     // Layers are first clipped to `layerStackSpaceRect'.  They are then translated and
     // scaled from `layerStackSpaceRect' to `orientedDisplaySpaceRect'.  Finally, they are rotated
@@ -446,10 +455,17 @@
     // will be scaled by a factor of 2 and translated by (20, 10). When orientation is 1, layers
     // will be additionally rotated by 90 degrees around the origin clockwise and translated by (W,
     // 0).
+    //
+    // Rect::INVALID_RECT sizes the space to the active resolution of the physical display, or the
+    // default dimensions of the virtual display surface.
+    //
     ui::Rotation orientation = ui::ROTATION_0;
     Rect layerStackSpaceRect = Rect::EMPTY_RECT;
     Rect orientedDisplaySpaceRect = Rect::EMPTY_RECT;
 
+    // Exclusive to virtual displays: The sink surface into which the virtual display is rendered,
+    // and an optional resolution that overrides its default dimensions.
+    sp<IGraphicBufferProducer> surface;
     uint32_t width = 0;
     uint32_t height = 0;
 
diff --git a/libs/gui/include/gui/LayerStatePermissions.h b/libs/gui/include/gui/LayerStatePermissions.h
index a90f30c..b6588a2 100644
--- a/libs/gui/include/gui/LayerStatePermissions.h
+++ b/libs/gui/include/gui/LayerStatePermissions.h
@@ -15,15 +15,14 @@
  */
 
 #include <stdint.h>
-#include <string>
-#include <unordered_map>
-
+#include <utils/String16.h>
+#include <vector>
 namespace android {
 class LayerStatePermissions {
 public:
     static uint32_t getTransactionPermissions(int pid, int uid);
 
 private:
-    static std::unordered_map<std::string, int> mPermissionMap;
+    static std::vector<std::pair<String16, int>> mPermissionMap;
 };
 } // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 39a59e4..bdcaaf2 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -215,6 +215,16 @@
                                   int8_t changeFrameRateStrategy);
     virtual status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    /**
+     * Set additional options to be passed when allocating a buffer. Only valid if IAllocator-V2
+     * or newer is available, otherwise will return INVALID_OPERATION. Only allowed to be called
+     * after connect and options are cleared when disconnect happens. Returns NO_INIT if not
+     * connected
+     */
+    status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options);
+#endif
+
 protected:
     virtual ~Surface();
 
@@ -302,6 +312,7 @@
     int dispatchGetLastQueuedBuffer(va_list args);
     int dispatchGetLastQueuedBuffer2(va_list args);
     int dispatchSetFrameTimelineInfo(va_list args);
+    int dispatchSetAdditionalOptions(va_list args);
 
     std::mutex mNameMutex;
     std::string mName;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 14e3dd5..9712907 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -18,6 +18,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+
 #include <set>
 #include <thread>
 #include <unordered_map>
@@ -374,17 +375,15 @@
 
     sp<SurfaceControl> mirrorDisplay(DisplayId displayId);
 
-    //! Create a virtual display
-    static sp<IBinder> createDisplay(const String8& displayName, bool secure,
-                                     float requestedRefereshRate = 0);
+    static const std::string kEmpty;
+    static sp<IBinder> createVirtualDisplay(const std::string& displayName, bool isSecure,
+                                            const std::string& uniqueId = kEmpty,
+                                            float requestedRefreshRate = 0);
 
-    //! Destroy a virtual display
-    static void destroyDisplay(const sp<IBinder>& display);
+    static status_t destroyVirtualDisplay(const sp<IBinder>& displayToken);
 
-    //! Get stable IDs for connected physical displays
     static std::vector<PhysicalDisplayId> getPhysicalDisplayIds();
 
-    //! Get token for a physical display given its stable ID
     static sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId);
 
     // Returns StalledTransactionInfo if a transaction from the provided pid has not been applied
@@ -431,6 +430,8 @@
         static std::mutex sApplyTokenMutex;
         void releaseBufferIfOverwriting(const layer_state_t& state);
         static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
+        // Tracks registered callbacks
+        sp<TransactionCompletedListener> mTransactionCompletedListener = nullptr;
 
     protected:
         std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> mComposerStates;
@@ -594,6 +595,7 @@
         Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace);
         Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc,
                                                 float currentBufferRatio, float desiredRatio);
+        Transaction& setDesiredHdrHeadroom(const sp<SurfaceControl>& sc, float desiredRatio);
         Transaction& setCachingHint(const sp<SurfaceControl>& sc, gui::CachingHint cachingHint);
         Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
         Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc,
@@ -717,6 +719,8 @@
 
         // Sets that this surface control and its children are trusted overlays for input
         Transaction& setTrustedOverlay(const sp<SurfaceControl>& sc, bool isTrustedOverlay);
+        Transaction& setTrustedOverlay(const sp<SurfaceControl>& sc,
+                                       gui::TrustedOverlay trustedOverlay);
 
         // Queues up transactions using this token in SurfaceFlinger.  By default, all transactions
         // from a client are placed on the same queue. This can be used to prevent multiple
@@ -743,9 +747,6 @@
                                          const Rect& destinationFrame);
         Transaction& setDropInputMode(const sp<SurfaceControl>& sc, gui::DropInputMode mode);
 
-        Transaction& enableBorder(const sp<SurfaceControl>& sc, bool shouldEnable, float width,
-                                  const half4& color);
-
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
 
@@ -825,6 +826,8 @@
                     nullptr);
     status_t removeWindowInfosListener(const sp<gui::WindowInfosListener>& windowInfosListener);
 
+    static void notifyShutdown();
+
 protected:
     ReleaseCallbackThread mReleaseCallbackThread;
 
@@ -849,7 +852,8 @@
     static status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
     static status_t captureDisplay(DisplayId, const gui::CaptureArgs&,
                                    const sp<IScreenCaptureListener>&);
-    static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
+    static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&,
+                                  bool sync);
 
     [[deprecated]] static status_t captureDisplay(DisplayId id,
                                                   const sp<IScreenCaptureListener>& listener) {
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 4d4c5e4..eb3be55 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -23,7 +23,7 @@
 #include <ftl/flags.h>
 #include <ftl/mixins.h>
 #include <gui/PidUid.h>
-#include <gui/constants.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Size.h>
@@ -176,6 +176,10 @@
                 static_cast<uint32_t>(os::InputConfig::INTERCEPTS_STYLUS),
         CLONE =
                 static_cast<uint32_t>(os::InputConfig::CLONE),
+        GLOBAL_STYLUS_BLOCKS_TOUCH =
+                static_cast<uint32_t>(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH),
+        SENSITIVE_FOR_PRIVACY =
+                static_cast<uint32_t>(os::InputConfig::SENSITIVE_FOR_PRIVACY),
         // clang-format on
     };
 
@@ -230,7 +234,7 @@
     Uid ownerUid = Uid::INVALID;
     std::string packageName;
     ftl::Flags<InputConfig> inputConfig;
-    int32_t displayId = ADISPLAY_ID_NONE;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
     InputApplicationInfo applicationInfo;
     bool replaceTouchableRegionWithCrop = false;
     wp<IBinder> touchableRegionCropHandle;
@@ -244,14 +248,14 @@
     // any other window.
     sp<IBinder> focusTransferTarget;
 
+    // Sets a property on this window indicating that its visible region should be considered when
+    // computing TrustedPresentation Thresholds.
+    bool canOccludePresentation = false;
+
     void setInputConfig(ftl::Flags<InputConfig> config, bool value);
 
     void addTouchableRegion(const Rect& region);
 
-    bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
-
-    bool frameContainsPoint(int32_t x, int32_t y) const;
-
     bool supportsSplitTouch() const;
 
     bool isSpy() const;
@@ -267,6 +271,8 @@
     status_t readFromParcel(const android::Parcel* parcel) override;
 };
 
+std::ostream& operator<<(std::ostream& out, const WindowInfo& window);
+
 /*
  * Handle for a window that can receive input.
  *
diff --git a/libs/gui/include/gui/constants.h b/libs/gui/include/gui/constants.h
deleted file mode 100644
index 8eab378..0000000
--- a/libs/gui/include/gui/constants.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <stdint.h>
-
-namespace android {
-
-/**
- * Invalid value for display size. Used when display size isn't available.
- */
-constexpr int32_t INVALID_DISPLAY_SIZE = 0;
-
-enum {
-    /* Used when an event is not associated with any display.
-     * Typically used for non-pointer events. */
-    ADISPLAY_ID_NONE = -1,
-
-    /* The default display id. */
-    ADISPLAY_ID_DEFAULT = 0,
-};
-
-} // namespace android
\ No newline at end of file
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index 3864699..a902a8c 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -16,3 +16,11 @@
   bug: "310927247"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "bq_extendedallocate"
+  namespace: "core_graphics"
+  description: "Add BQ support for allocate with extended options"
+  bug: "268382490"
+  is_fixed_read_only: true
+}
diff --git a/libs/gui/rust/aidl_types/Android.bp b/libs/gui/rust/aidl_types/Android.bp
new file mode 100644
index 0000000..794f69e
--- /dev/null
+++ b/libs/gui/rust/aidl_types/Android.bp
@@ -0,0 +1,23 @@
+rust_defaults {
+    name: "libgui_aidl_types_defaults",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libbinder_rs",
+    ],
+}
+
+rust_library {
+    name: "libgui_aidl_types_rs",
+    crate_name: "gui_aidl_types_rs",
+    defaults: ["libgui_aidl_types_defaults"],
+
+    // Currently necessary for host builds
+    // TODO(b/31559095): bionic on host should define this
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
+}
diff --git a/libs/gui/rust/aidl_types/src/lib.rs b/libs/gui/rust/aidl_types/src/lib.rs
new file mode 100644
index 0000000..fead018
--- /dev/null
+++ b/libs/gui/rust/aidl_types/src/lib.rs
@@ -0,0 +1,55 @@
+// Copyright (C) 2024 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.
+
+//! Rust wrapper for libgui AIDL types.
+
+use binder::{
+    binder_impl::{BorrowedParcel, UnstructuredParcelable},
+    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
+    StatusCode,
+};
+
+macro_rules! stub_unstructured_parcelable {
+    ($name:ident) => {
+        /// Unimplemented stub parcelable.
+        #[derive(Debug, Default)]
+        pub struct $name(());
+
+        impl UnstructuredParcelable for $name {
+            fn write_to_parcel(&self, _parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+                todo!()
+            }
+
+            fn from_parcel(_parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+                todo!()
+            }
+        }
+
+        impl_deserialize_for_unstructured_parcelable!($name);
+        impl_serialize_for_unstructured_parcelable!($name);
+    };
+}
+
+stub_unstructured_parcelable!(BitTube);
+stub_unstructured_parcelable!(CaptureArgs);
+stub_unstructured_parcelable!(DisplayCaptureArgs);
+stub_unstructured_parcelable!(DisplayInfo);
+stub_unstructured_parcelable!(LayerCaptureArgs);
+stub_unstructured_parcelable!(LayerDebugInfo);
+stub_unstructured_parcelable!(LayerMetadata);
+stub_unstructured_parcelable!(ParcelableVsyncEventData);
+stub_unstructured_parcelable!(ScreenCaptureResults);
+stub_unstructured_parcelable!(VsyncEventData);
+stub_unstructured_parcelable!(WindowInfo);
+stub_unstructured_parcelable!(WindowInfosUpdate);
diff --git a/libs/gui/sysprop/Android.bp b/libs/gui/sysprop/Android.bp
index cc33e4c..386767b 100644
--- a/libs/gui/sysprop/Android.bp
+++ b/libs/gui/sysprop/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 sysprop_library {
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index e606b99..ea8acbb 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -21,8 +21,10 @@
     cppflags: [
         "-Wall",
         "-Werror",
-        "-Wno-extra",
+        "-Wextra",
+        "-Wthread-safety",
         "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_EXTENDEDALLOCATE=true",
     ],
 
     srcs: [
@@ -30,6 +32,7 @@
         "BLASTBufferQueue_test.cpp",
         "BufferItemConsumer_test.cpp",
         "BufferQueue_test.cpp",
+        "Choreographer_test.cpp",
         "CompositorTiming_test.cpp",
         "CpuConsumer_test.cpp",
         "EndToEndNativeInputTest.cpp",
@@ -61,6 +64,7 @@
         "libSurfaceFlingerProp",
         "libGLESv1_CM",
         "libinput",
+        "libnativedisplay",
     ],
 
     static_libs: [
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index ea7078d..946ff05 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -18,6 +18,7 @@
 
 #include <gui/BLASTBufferQueue.h>
 
+#include <android-base/thread_annotations.h>
 #include <android/hardware/graphics/common/1.2/types.h>
 #include <gui/AidlStatusUtil.h>
 #include <gui/BufferQueueCore.h>
@@ -61,7 +62,8 @@
     }
 
     void waitOnNumberReleased(int32_t expectedNumReleased) {
-        std::unique_lock<std::mutex> lock(mMutex);
+        std::unique_lock lock{mMutex};
+        base::ScopedLockAssertion assumeLocked(mMutex);
         while (mNumReleased < expectedNumReleased) {
             ASSERT_NE(mReleaseCallback.wait_for(lock, std::chrono::seconds(3)),
                       std::cv_status::timeout)
@@ -134,11 +136,18 @@
 
     void clearSyncTransaction() { mBlastBufferQueueAdapter->clearSyncTransaction(); }
 
-    int getWidth() { return mBlastBufferQueueAdapter->mSize.width; }
+    int getWidth() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
+        return mBlastBufferQueueAdapter->mSize.width;
+    }
 
-    int getHeight() { return mBlastBufferQueueAdapter->mSize.height; }
+    int getHeight() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
+        return mBlastBufferQueueAdapter->mSize.height;
+    }
 
     std::function<void(Transaction*)> getTransactionReadyCallback() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
         return mBlastBufferQueueAdapter->mTransactionReadyCallback;
     }
 
@@ -147,6 +156,7 @@
     }
 
     const sp<SurfaceControl> getSurfaceControl() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
         return mBlastBufferQueueAdapter->mSurfaceControl;
     }
 
@@ -156,6 +166,7 @@
 
     void waitForCallbacks() {
         std::unique_lock lock{mBlastBufferQueueAdapter->mMutex};
+        base::ScopedLockAssertion assumeLocked(mBlastBufferQueueAdapter->mMutex);
         // Wait until all but one of the submitted buffers have been released.
         while (mBlastBufferQueueAdapter->mSubmitted.size() > 1) {
             mBlastBufferQueueAdapter->mCallbackCV.wait(lock);
@@ -166,8 +177,8 @@
         mBlastBufferQueueAdapter->waitForCallback(frameNumber);
     }
 
-    void validateNumFramesSubmitted(int64_t numFramesSubmitted) {
-        std::unique_lock lock{mBlastBufferQueueAdapter->mMutex};
+    void validateNumFramesSubmitted(size_t numFramesSubmitted) {
+        std::scoped_lock lock{mBlastBufferQueueAdapter->mMutex};
         ASSERT_EQ(numFramesSubmitted, mBlastBufferQueueAdapter->mSubmitted.size());
     }
 
@@ -201,7 +212,7 @@
         mDisplayWidth = resolution.getWidth();
         mDisplayHeight = resolution.getHeight();
         ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight,
-              displayState.orientation);
+              static_cast<int32_t>(displayState.orientation));
 
         mRootSurfaceControl = mClient->createSurface(String8("RootTestSurface"), mDisplayWidth,
                                                      mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
@@ -240,8 +251,8 @@
 
     void fillBuffer(uint32_t* bufData, Rect rect, uint32_t stride, uint8_t r, uint8_t g,
                     uint8_t b) {
-        for (uint32_t row = rect.top; row < rect.bottom; row++) {
-            for (uint32_t col = rect.left; col < rect.right; col++) {
+        for (int32_t row = rect.top; row < rect.bottom; row++) {
+            for (int32_t col = rect.left; col < rect.right; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
                 *pixel = r;
                 *(pixel + 1) = g;
@@ -271,16 +282,16 @@
                             bool outsideRegion = false) {
         sp<GraphicBuffer>& captureBuf = mCaptureResults.buffer;
         const auto epsilon = 3;
-        const auto width = captureBuf->getWidth();
-        const auto height = captureBuf->getHeight();
+        const int32_t width = static_cast<int32_t>(captureBuf->getWidth());
+        const int32_t height = static_cast<int32_t>(captureBuf->getHeight());
         const auto stride = captureBuf->getStride();
 
         uint32_t* bufData;
         captureBuf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_READ_OFTEN),
                          reinterpret_cast<void**>(&bufData));
 
-        for (uint32_t row = 0; row < height; row++) {
-            for (uint32_t col = 0; col < width; col++) {
+        for (int32_t row = 0; row < height; row++) {
+            for (int32_t col = 0; col < width; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
                 ASSERT_NE(nullptr, pixel);
                 bool inRegion;
@@ -352,8 +363,8 @@
     // create BLASTBufferQueue adapter associated with this surface
     BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
     ASSERT_EQ(mSurfaceControl, adapter.getSurfaceControl());
-    ASSERT_EQ(mDisplayWidth, adapter.getWidth());
-    ASSERT_EQ(mDisplayHeight, adapter.getHeight());
+    ASSERT_EQ(static_cast<int32_t>(mDisplayWidth), adapter.getWidth());
+    ASSERT_EQ(static_cast<int32_t>(mDisplayHeight), adapter.getHeight());
     ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback());
 }
 
@@ -371,10 +382,10 @@
 
     int32_t width;
     igbProducer->query(NATIVE_WINDOW_WIDTH, &width);
-    ASSERT_EQ(mDisplayWidth / 2, width);
+    ASSERT_EQ(static_cast<int32_t>(mDisplayWidth) / 2, width);
     int32_t height;
     igbProducer->query(NATIVE_WINDOW_HEIGHT, &height);
-    ASSERT_EQ(mDisplayHeight / 2, height);
+    ASSERT_EQ(static_cast<int32_t>(mDisplayHeight) / 2, height);
 }
 
 TEST_F(BLASTBufferQueueTest, SyncNextTransaction) {
@@ -476,7 +487,7 @@
         ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
         allocated.push_back({slot, fence});
     }
-    for (int i = 0; i < allocated.size(); i++) {
+    for (size_t i = 0; i < allocated.size(); i++) {
         igbProducer->cancelBuffer(allocated[i].first, allocated[i].second);
     }
 
@@ -1313,14 +1324,14 @@
     // Before connecting to the surface, we do not get a valid transform hint
     int transformHint;
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_0, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_0, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     ASSERT_EQ(NO_ERROR,
               surface->connect(NATIVE_WINDOW_API_CPU, new TestProducerListener(igbProducer)));
 
     // After connecting to the surface, we should get the correct hint.
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_90, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_90, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     ANativeWindow_Buffer buffer;
     surface->lock(&buffer, nullptr /* inOutDirtyBounds */);
@@ -1331,13 +1342,13 @@
 
     // The hint does not change and matches the value used when dequeueing the buffer.
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_90, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_90, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     surface->unlockAndPost();
 
     // After queuing the buffer, we get the updated transform hint
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_0, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_0, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     adapter.waitForCallbacks();
 }
@@ -1573,7 +1584,7 @@
     FrameEvents* events = nullptr;
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1591,7 +1602,7 @@
     ASSERT_NE(nullptr, events);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1606,7 +1617,7 @@
     // we should also have gotten the initial values for the next frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
 
@@ -1622,7 +1633,7 @@
     // Check the first frame...
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
     ASSERT_GE(events->latchTime, postedTimeA);
@@ -1636,7 +1647,7 @@
     // ...and the second
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
@@ -1650,7 +1661,7 @@
     // ...and finally the third!
     events = history.getFrame(3);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(3, events->frameNumber);
+    ASSERT_EQ(3u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeC, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeC);
 
@@ -1677,7 +1688,7 @@
     FrameEvents* events = nullptr;
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1692,7 +1703,7 @@
     ASSERT_NE(nullptr, events);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1715,7 +1726,7 @@
     adapter.waitForCallback(3);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1729,7 +1740,7 @@
     // we should also have gotten values for the presented frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
@@ -1751,7 +1762,7 @@
 
     // frame number, requestedPresentTime, and postTime should not have changed
     events = history.getFrame(1);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1765,7 +1776,7 @@
     // we should also have gotten values for the presented frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index df7739c..272c5ed 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -28,6 +28,8 @@
 
 #include <ui/GraphicBuffer.h>
 
+#include <android-base/properties.h>
+
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
@@ -47,6 +49,10 @@
 
 using namespace std::chrono_literals;
 
+static bool IsCuttlefish() {
+    return ::android::base::GetProperty("ro.product.board", "") == "cutf";
+}
+
 namespace android {
 using namespace com::android::graphics::libgui;
 
@@ -119,8 +125,7 @@
     }
 
     sp<IServiceManager> serviceManager = defaultServiceManager();
-    sp<IBinder> binderProducer =
-        serviceManager->getService(PRODUCER_NAME);
+    sp<IBinder> binderProducer = serviceManager->waitForService(PRODUCER_NAME);
     mProducer = interface_cast<IGraphicBufferProducer>(binderProducer);
     EXPECT_TRUE(mProducer != nullptr);
     sp<IBinder> binderConsumer =
@@ -1114,7 +1119,7 @@
 
     // Check onBuffersDiscarded is called with correct slots
     auto buffersDiscarded = pl->getDiscardedSlots();
-    ASSERT_EQ(buffersDiscarded.size(), 1);
+    ASSERT_EQ(buffersDiscarded.size(), 1u);
     ASSERT_EQ(buffersDiscarded[0], releasedSlot);
 
     // Check no free buffers in dump
@@ -1239,7 +1244,7 @@
     ASSERT_EQ(OK, mConsumer->detachBuffer(item.mSlot));
 
     // Check whether the slot from IProducerListener is same to the detached slot.
-    ASSERT_EQ(pl->getDetachedSlots().size(), 1);
+    ASSERT_EQ(pl->getDetachedSlots().size(), 1u);
     ASSERT_EQ(pl->getDetachedSlots()[0], slots[1]);
 
     // Dequeue another buffer.
@@ -1440,4 +1445,55 @@
     EXPECT_EQ(nullptr, bufferConsumer.get());
 }
 
+TEST_F(BufferQueueTest, TestAdditionalOptions) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<BufferItemConsumer> bufferConsumer =
+            sp<BufferItemConsumer>::make(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 2);
+    ASSERT_NE(nullptr, bufferConsumer.get());
+    sp<Surface> surface = sp<Surface>::make(producer);
+    native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
+    native_window_set_buffers_dimensions(surface.get(), 100, 100);
+
+    std::array<AHardwareBufferLongOptions, 1> extras = {{
+            {.name = "android.hardware.graphics.common.Dataspace", ADATASPACE_DISPLAY_P3},
+    }};
+
+    ASSERT_EQ(NO_INIT,
+              native_window_set_buffers_additional_options(surface.get(), extras.data(),
+                                                           extras.size()));
+
+    if (!IsCuttlefish()) {
+        GTEST_SKIP() << "Not cuttlefish";
+    }
+
+    ASSERT_EQ(OK, native_window_api_connect(surface.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(OK,
+              native_window_set_buffers_additional_options(surface.get(), extras.data(),
+                                                           extras.size()));
+
+    ANativeWindowBuffer* windowBuffer = nullptr;
+    int fence = -1;
+    ASSERT_EQ(OK, ANativeWindow_dequeueBuffer(surface.get(), &windowBuffer, &fence));
+
+    AHardwareBuffer* buffer = ANativeWindowBuffer_getHardwareBuffer(windowBuffer);
+    ASSERT_TRUE(buffer);
+    ADataSpace dataSpace = AHardwareBuffer_getDataSpace(buffer);
+    EXPECT_EQ(ADATASPACE_DISPLAY_P3, dataSpace);
+
+    ANativeWindow_cancelBuffer(surface.get(), windowBuffer, -1);
+
+    // Check that reconnecting properly clears the options
+    ASSERT_EQ(OK, native_window_api_disconnect(surface.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(OK, native_window_api_connect(surface.get(), NATIVE_WINDOW_API_CPU));
+
+    ASSERT_EQ(OK, ANativeWindow_dequeueBuffer(surface.get(), &windowBuffer, &fence));
+    buffer = ANativeWindowBuffer_getHardwareBuffer(windowBuffer);
+    ASSERT_TRUE(buffer);
+    dataSpace = AHardwareBuffer_getDataSpace(buffer);
+    EXPECT_EQ(ADATASPACE_UNKNOWN, dataSpace);
+}
+
 } // namespace android
diff --git a/libs/gui/tests/Choreographer_test.cpp b/libs/gui/tests/Choreographer_test.cpp
new file mode 100644
index 0000000..2ac2550
--- /dev/null
+++ b/libs/gui/tests/Choreographer_test.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Choreographer_test"
+
+#include <android-base/stringprintf.h>
+#include <android/choreographer.h>
+#include <gtest/gtest.h>
+#include <gui/Choreographer.h>
+#include <utils/Looper.h>
+#include <chrono>
+#include <future>
+#include <string>
+
+namespace android {
+class ChoreographerTest : public ::testing::Test {};
+
+struct VsyncCallback {
+    std::atomic<bool> completePromise{false};
+    std::chrono::nanoseconds frameTime{0LL};
+    std::chrono::nanoseconds receivedCallbackTime{0LL};
+
+    void onVsyncCallback(const AChoreographerFrameCallbackData* callbackData) {
+        frameTime = std::chrono::nanoseconds{
+                AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData)};
+        receivedCallbackTime = std::chrono::nanoseconds{systemTime(SYSTEM_TIME_MONOTONIC)};
+        completePromise.store(true);
+    }
+
+    bool callbackReceived() { return completePromise.load(); }
+};
+
+static void vsyncCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
+    VsyncCallback* cb = static_cast<VsyncCallback*>(data);
+    cb->onVsyncCallback(callbackData);
+}
+
+TEST_F(ChoreographerTest, InputCallbackBeforeAnimation) {
+    sp<Looper> looper = Looper::prepare(0);
+    Choreographer* choreographer = Choreographer::getForThread();
+    VsyncCallback animationCb;
+    VsyncCallback inputCb;
+
+    choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &animationCb, 0,
+                                            CALLBACK_ANIMATION);
+    choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &inputCb, 0,
+                                            CALLBACK_INPUT);
+
+    nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    nsecs_t currTime;
+    int pollResult;
+    do {
+        pollResult = looper->pollOnce(16);
+        currTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    } while (!(inputCb.callbackReceived() && animationCb.callbackReceived()) &&
+             (pollResult != Looper::POLL_TIMEOUT && pollResult != Looper::POLL_ERROR) &&
+             (currTime - startTime < 3000));
+
+    ASSERT_TRUE(inputCb.callbackReceived()) << "did not receive input callback";
+    ASSERT_TRUE(animationCb.callbackReceived()) << "did not receive animation callback";
+
+    ASSERT_EQ(inputCb.frameTime, animationCb.frameTime)
+            << android::base::StringPrintf("input and animation callback frame times don't match. "
+                                           "inputFrameTime=%lld  animationFrameTime=%lld",
+                                           inputCb.frameTime.count(),
+                                           animationCb.frameTime.count());
+
+    ASSERT_LT(inputCb.receivedCallbackTime, animationCb.receivedCallbackTime)
+            << android::base::StringPrintf("input callback was not called first. "
+                                           "inputCallbackTime=%lld  animationCallbackTime=%lld",
+                                           inputCb.frameTime.count(),
+                                           animationCb.frameTime.count());
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/tests/DisplayInfo_test.cpp b/libs/gui/tests/DisplayInfo_test.cpp
index df3329c..4df76b1 100644
--- a/libs/gui/tests/DisplayInfo_test.cpp
+++ b/libs/gui/tests/DisplayInfo_test.cpp
@@ -28,7 +28,7 @@
 
 TEST(DisplayInfo, Parcelling) {
     DisplayInfo info;
-    info.displayId = 42;
+    info.displayId = ui::LogicalDisplayId{42};
     info.logicalWidth = 99;
     info.logicalHeight = 78;
     info.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
diff --git a/libs/gui/tests/DisplayedContentSampling_test.cpp b/libs/gui/tests/DisplayedContentSampling_test.cpp
index 0a2750a..bffb3f0 100644
--- a/libs/gui/tests/DisplayedContentSampling_test.cpp
+++ b/libs/gui/tests/DisplayedContentSampling_test.cpp
@@ -116,10 +116,10 @@
     EXPECT_EQ(OK, status);
     if (stats.numFrames <= 0) return;
 
-    if (componentMask & (0x1 << 0)) EXPECT_NE(0, stats.component_0_sample.size());
-    if (componentMask & (0x1 << 1)) EXPECT_NE(0, stats.component_1_sample.size());
-    if (componentMask & (0x1 << 2)) EXPECT_NE(0, stats.component_2_sample.size());
-    if (componentMask & (0x1 << 3)) EXPECT_NE(0, stats.component_3_sample.size());
+    if (componentMask & (0x1 << 0)) EXPECT_NE(0u, stats.component_0_sample.size());
+    if (componentMask & (0x1 << 1)) EXPECT_NE(0u, stats.component_1_sample.size());
+    if (componentMask & (0x1 << 2)) EXPECT_NE(0u, stats.component_2_sample.size());
+    if (componentMask & (0x1 << 3)) EXPECT_NE(0u, stats.component_3_sample.size());
 }
 
 } // namespace android
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index d4b8dbe..7d0b512 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -24,6 +24,7 @@
 
 #include <memory>
 
+#include <android-base/thread_annotations.h>
 #include <android/gui/BnWindowInfosReportedListener.h>
 #include <android/keycodes.h>
 #include <android/native_window.h>
@@ -41,6 +42,7 @@
 #include <android/os/IInputFlinger.h>
 #include <gui/WindowInfo.h>
 #include <input/Input.h>
+#include <input/InputConsumer.h>
 #include <input/InputTransport.h>
 
 #include <ui/DisplayMode.h>
@@ -58,16 +60,23 @@
 using android::gui::TouchOcclusionMode;
 using android::gui::WindowInfo;
 
-namespace android::test {
+namespace android {
+namespace {
+ui::LogicalDisplayId toDisplayId(ui::LayerStack layerStack) {
+    return ui::LogicalDisplayId{static_cast<int32_t>(layerStack.id)};
+}
+} // namespace
+namespace test {
 
 using Transaction = SurfaceComposerClient::Transaction;
 
 sp<IInputFlinger> getInputFlinger() {
-   sp<IBinder> input(defaultServiceManager()->getService(
-            String16("inputflinger")));
+    sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger")));
     if (input == nullptr) {
         ALOGE("Failed to link to input service");
-    } else { ALOGE("Linked to input"); }
+    } else {
+        ALOGE("Linked to input");
+    }
     return interface_cast<IInputFlinger>(input);
 }
 
@@ -78,34 +87,40 @@
 class SynchronousWindowInfosReportedListener : public gui::BnWindowInfosReportedListener {
 public:
     binder::Status onWindowInfosReported() override {
-        std::lock_guard<std::mutex> lock{mMutex};
+        std::scoped_lock lock{mLock};
         mWindowInfosReported = true;
         mConditionVariable.notify_one();
         return binder::Status::ok();
     }
 
     void wait() {
-        std::unique_lock<std::mutex> lock{mMutex};
-        mConditionVariable.wait(lock, [&] { return mWindowInfosReported; });
+        std::unique_lock lock{mLock};
+        android::base::ScopedLockAssertion assumeLocked(mLock);
+        mConditionVariable.wait(lock, [&]() REQUIRES(mLock) { return mWindowInfosReported; });
     }
 
 private:
-    std::mutex mMutex;
+    std::mutex mLock;
     std::condition_variable mConditionVariable;
-    bool mWindowInfosReported{false};
+    bool mWindowInfosReported GUARDED_BY(mLock){false};
 };
 
 class InputSurface {
 public:
-    InputSurface(const sp<SurfaceControl> &sc, int width, int height, bool noInputChannel = false) {
+    InputSurface(const sp<SurfaceControl>& sc, int width, int height, bool noInputChannel = false) {
         mSurfaceControl = sc;
 
         mInputFlinger = getInputFlinger();
         if (noInputChannel) {
             mInputInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, true);
         } else {
-            mClientChannel = std::make_shared<InputChannel>();
-            mInputFlinger->createInputChannel("testchannels", mClientChannel.get());
+            android::os::InputChannelCore tempChannel;
+            android::binder::Status result =
+                    mInputFlinger->createInputChannel("testchannels", &tempChannel);
+            if (!result.isOk()) {
+                ADD_FAILURE() << "binder call to createInputChannel failed";
+            }
+            mClientChannel = InputChannel::create(std::move(tempChannel));
             mInputInfo.token = mClientChannel->getConnectionToken();
             mInputConsumer = new InputConsumer(mClientChannel);
         }
@@ -123,7 +138,7 @@
         mInputInfo.applicationInfo = aInfo;
     }
 
-    static std::unique_ptr<InputSurface> makeColorInputSurface(const sp<SurfaceComposerClient> &scc,
+    static std::unique_ptr<InputSurface> makeColorInputSurface(const sp<SurfaceComposerClient>& scc,
                                                                int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Surface"), 0 /* bufHeight */, 0 /* bufWidth */,
@@ -133,7 +148,7 @@
     }
 
     static std::unique_ptr<InputSurface> makeBufferInputSurface(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Buffer Surface"), width, height,
                                    PIXEL_FORMAT_RGBA_8888, 0 /* flags */);
@@ -141,7 +156,7 @@
     }
 
     static std::unique_ptr<InputSurface> makeContainerInputSurface(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Container Surface"), 0 /* bufHeight */,
                                    0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888,
@@ -150,7 +165,7 @@
     }
 
     static std::unique_ptr<InputSurface> makeContainerInputSurfaceNoInputChannel(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Container Surface"), 100 /* height */,
                                    100 /* width */, PIXEL_FORMAT_RGBA_8888,
@@ -160,7 +175,7 @@
     }
 
     static std::unique_ptr<InputSurface> makeCursorInputSurface(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Cursor Surface"), 0 /* bufHeight */,
                                    0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888,
@@ -168,10 +183,10 @@
         return std::make_unique<InputSurface>(surfaceControl, width, height);
     }
 
-    InputEvent *consumeEvent(int timeoutMs = 3000) {
-        waitForEventAvailable(timeoutMs);
+    InputEvent* consumeEvent(std::chrono::milliseconds timeout = 3000ms) {
+        mClientChannel->waitForMessage(timeout);
 
-        InputEvent *ev;
+        InputEvent* ev;
         uint32_t seqId;
         status_t consumed = mInputConsumer->consume(&mInputEventFactory, true, -1, &seqId, &ev);
         if (consumed != OK) {
@@ -183,14 +198,14 @@
     }
 
     void assertFocusChange(bool hasFocus) {
-        InputEvent *ev = consumeEvent();
+        InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::FOCUS, ev->getType());
-        FocusEvent *focusEvent = static_cast<FocusEvent *>(ev);
+        FocusEvent* focusEvent = static_cast<FocusEvent*>(ev);
         EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
     }
 
-    void expectTap(int x, int y) {
+    void expectTap(float x, float y) {
         InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
@@ -209,10 +224,10 @@
     }
 
     void expectTapWithFlag(int x, int y, int32_t flags) {
-        InputEvent *ev = consumeEvent();
+        InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
-        MotionEvent *mev = static_cast<MotionEvent *>(ev);
+        MotionEvent* mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
         EXPECT_EQ(x, mev->getX(0));
         EXPECT_EQ(y, mev->getY(0));
@@ -221,18 +236,18 @@
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
-        mev = static_cast<MotionEvent *>(ev);
+        mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
         EXPECT_EQ(flags, mev->getFlags() & flags);
     }
 
     void expectTapInDisplayCoordinates(int displayX, int displayY) {
-        InputEvent *ev = consumeEvent();
+        InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
-        MotionEvent *mev = static_cast<MotionEvent *>(ev);
+        MotionEvent* mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
-        const PointerCoords &coords = *mev->getRawPointerCoords(0 /*pointerIndex*/);
+        const PointerCoords& coords = *mev->getRawPointerCoords(0 /*pointerIndex*/);
         EXPECT_EQ(displayX, coords.getX());
         EXPECT_EQ(displayY, coords.getY());
         EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
@@ -240,16 +255,16 @@
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
-        mev = static_cast<MotionEvent *>(ev);
+        mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
         EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
     }
 
-    void expectKey(uint32_t keycode) {
-        InputEvent *ev = consumeEvent();
+    void expectKey(int32_t keycode) {
+        InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::KEY, ev->getType());
-        KeyEvent *keyEvent = static_cast<KeyEvent *>(ev);
+        KeyEvent* keyEvent = static_cast<KeyEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, keyEvent->getAction());
         EXPECT_EQ(keycode, keyEvent->getKeyCode());
         EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS);
@@ -257,12 +272,17 @@
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::KEY, ev->getType());
-        keyEvent = static_cast<KeyEvent *>(ev);
+        keyEvent = static_cast<KeyEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, keyEvent->getAction());
         EXPECT_EQ(keycode, keyEvent->getKeyCode());
         EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS);
     }
 
+    void assertNoEvent() {
+        InputEvent* event = consumeEvent(/*timeout=*/100ms);
+        ASSERT_EQ(event, nullptr) << "Expected no event, but got " << *event;
+    }
+
     virtual ~InputSurface() {
         if (mClientChannel) {
             mInputFlinger->removeInputChannel(mClientChannel->getConnectionToken());
@@ -270,7 +290,7 @@
     }
 
     virtual void doTransaction(
-            std::function<void(SurfaceComposerClient::Transaction &, const sp<SurfaceControl> &)>
+            std::function<void(SurfaceComposerClient::Transaction&, const sp<SurfaceControl>&)>
                     transactionBody) {
         SurfaceComposerClient::Transaction t;
         transactionBody(t, mSurfaceControl);
@@ -291,26 +311,17 @@
         reportedListener->wait();
     }
 
-    void requestFocus(int displayId = ADISPLAY_ID_DEFAULT) {
+    void requestFocus(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT) {
         SurfaceComposerClient::Transaction t;
         FocusRequest request;
         request.token = mInputInfo.token;
         request.windowName = mInputInfo.name;
         request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
-        request.displayId = displayId;
+        request.displayId = displayId.val();
         t.setFocusedWindow(request);
         t.apply(true);
     }
 
-private:
-    void waitForEventAvailable(int timeoutMs) {
-        struct pollfd fd;
-
-        fd.fd = mClientChannel->getFd();
-        fd.events = POLLIN;
-        poll(&fd, 1, timeoutMs);
-    }
-
 public:
     sp<SurfaceControl> mSurfaceControl;
     std::shared_ptr<InputChannel> mClientChannel;
@@ -324,7 +335,7 @@
 
 class BlastInputSurface : public InputSurface {
 public:
-    BlastInputSurface(const sp<SurfaceControl> &sc, const sp<SurfaceControl> &parentSc, int width,
+    BlastInputSurface(const sp<SurfaceControl>& sc, const sp<SurfaceControl>& parentSc, int width,
                       int height)
           : InputSurface(sc, width, height) {
         mParentSurfaceControl = parentSc;
@@ -333,7 +344,7 @@
     ~BlastInputSurface() = default;
 
     static std::unique_ptr<BlastInputSurface> makeBlastInputSurface(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> parentSc =
                 scc->createSurface(String8("Test Parent Surface"), 0 /* bufHeight */,
                                    0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888,
@@ -348,7 +359,7 @@
     }
 
     void doTransaction(
-            std::function<void(SurfaceComposerClient::Transaction &, const sp<SurfaceControl> &)>
+            std::function<void(SurfaceComposerClient::Transaction&, const sp<SurfaceControl>&)>
                     transactionBody) override {
         SurfaceComposerClient::Transaction t;
         transactionBody(t, mParentSurfaceControl);
@@ -375,9 +386,7 @@
 
 class InputSurfacesTest : public ::testing::Test {
 public:
-    InputSurfacesTest() {
-        ProcessState::self()->startThreadPool();
-    }
+    InputSurfacesTest() { ProcessState::self()->startThreadPool(); }
 
     void SetUp() {
         mComposerClient = new SurfaceComposerClient;
@@ -397,15 +406,13 @@
         mBufferPostDelay = static_cast<int32_t>(1e6 / mode.peakRefreshRate) * 3;
     }
 
-    void TearDown() {
-        mComposerClient->dispose();
-    }
+    void TearDown() { mComposerClient->dispose(); }
 
     std::unique_ptr<InputSurface> makeSurface(int width, int height) {
         return InputSurface::makeColorInputSurface(mComposerClient, width, height);
     }
 
-    void postBuffer(const sp<SurfaceControl> &layer, int32_t w, int32_t h) {
+    void postBuffer(const sp<SurfaceControl>& layer, int32_t w, int32_t h) {
         int64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
                 BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
         sp<GraphicBuffer> buffer =
@@ -418,31 +425,31 @@
     int32_t mBufferPostDelay;
 };
 
-void injectTapOnDisplay(int x, int y, int displayId) {
+void injectTapOnDisplay(int x, int y, ui::LogicalDisplayId displayId) {
     char *buf1, *buf2, *bufDisplayId;
     asprintf(&buf1, "%d", x);
     asprintf(&buf2, "%d", y);
-    asprintf(&bufDisplayId, "%d", displayId);
+    asprintf(&bufDisplayId, "%d", displayId.val());
     if (fork() == 0) {
         execlp("input", "input", "-d", bufDisplayId, "tap", buf1, buf2, NULL);
     }
 }
 
 void injectTap(int x, int y) {
-    injectTapOnDisplay(x, y, ADISPLAY_ID_DEFAULT);
+    injectTapOnDisplay(x, y, ui::LogicalDisplayId::DEFAULT);
 }
 
-void injectKeyOnDisplay(uint32_t keycode, int displayId) {
+void injectKeyOnDisplay(uint32_t keycode, ui::LogicalDisplayId displayId) {
     char *buf1, *bufDisplayId;
     asprintf(&buf1, "%d", keycode);
-    asprintf(&bufDisplayId, "%d", displayId);
+    asprintf(&bufDisplayId, "%d", displayId.val());
     if (fork() == 0) {
         execlp("input", "input", "-d", bufDisplayId, "keyevent", buf1, NULL);
     }
 }
 
 void injectKey(uint32_t keycode) {
-    injectKeyOnDisplay(keycode, ADISPLAY_ID_NONE);
+    injectKeyOnDisplay(keycode, ui::LogicalDisplayId::INVALID);
 }
 
 TEST_F(InputSurfacesTest, can_receive_input) {
@@ -473,12 +480,8 @@
     injectTap(101, 101);
     surface->expectTap(1, 1);
 
-    surface2->doTransaction([](auto &t, auto &sc) {
-         t.setPosition(sc, 100, 100);
-    });
-    surface->doTransaction([](auto &t, auto &sc) {
-         t.setPosition(sc, 200, 200);
-    });
+    surface2->doTransaction([](auto& t, auto& sc) { t.setPosition(sc, 100, 100); });
+    surface->doTransaction([](auto& t, auto& sc) { t.setPosition(sc, 200, 200); });
 
     injectTap(101, 101);
     surface2->expectTap(1, 1);
@@ -494,23 +497,17 @@
     surface->showAt(10, 10);
     surface2->showAt(10, 10);
 
-    surface->doTransaction([](auto &t, auto &sc) {
-         t.setLayer(sc, LAYER_BASE + 1);
-    });
+    surface->doTransaction([](auto& t, auto& sc) { t.setLayer(sc, LAYER_BASE + 1); });
 
     injectTap(11, 11);
     surface->expectTap(1, 1);
 
-    surface2->doTransaction([](auto &t, auto &sc) {
-         t.setLayer(sc, LAYER_BASE + 1);
-    });
+    surface2->doTransaction([](auto& t, auto& sc) { t.setLayer(sc, LAYER_BASE + 1); });
 
     injectTap(11, 11);
     surface2->expectTap(1, 1);
 
-    surface2->doTransaction([](auto &t, auto &sc) {
-         t.hide(sc);
-    });
+    surface2->doTransaction([](auto& t, auto& sc) { t.hide(sc); });
 
     injectTap(11, 11);
     surface->expectTap(1, 1);
@@ -559,7 +556,7 @@
     childSurface->mInputInfo.surfaceInset = 10;
     childSurface->showAt(100, 100);
 
-    childSurface->doTransaction([&](auto &t, auto &sc) {
+    childSurface->doTransaction([&](auto& t, auto& sc) {
         t.setPosition(sc, -5, -5);
         t.reparent(sc, parentSurface->mSurfaceControl);
     });
@@ -580,7 +577,7 @@
     fgSurface->mInputInfo.surfaceInset = 5;
     fgSurface->showAt(100, 100);
 
-    fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 4.0); });
+    fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 4.0); });
 
     // expect = touch / scale - inset
     injectTap(112, 124);
@@ -599,7 +596,7 @@
     fgSurface->mInputInfo.surfaceInset = INT32_MAX;
     fgSurface->showAt(100, 100);
 
-    fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
+    fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
 
     // expect no crash for overflow, and inset size to be clamped to surface size
     injectTap(112, 124);
@@ -615,7 +612,7 @@
 
     // A tap within the surface but outside the touchable region should not be sent to the surface.
     injectTap(20, 30);
-    EXPECT_EQ(surface->consumeEvent(200 /*timeoutMs*/), nullptr);
+    EXPECT_EQ(surface->consumeEvent(/*timeout=*/200ms), nullptr);
 
     injectTap(31, 52);
     surface->expectTap(20, 30);
@@ -648,7 +645,7 @@
     fgSurface->mInputInfo.touchableRegion.orSelf(Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX});
     fgSurface->showAt(0, 0);
 
-    fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
+    fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
 
     // Expect no crash for overflow.
     injectTap(12, 24);
@@ -658,7 +655,7 @@
 // Ensure we ignore transparent region when getting screen bounds when positioning input frame.
 TEST_F(InputSurfacesTest, input_ignores_transparent_region) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         Region transparentRegion(Rect(0, 0, 10, 10));
         t.setTransparentRegionHint(sc, transparentRegion);
     });
@@ -699,7 +696,7 @@
     injectTap(11, 11);
     bufferSurface->expectTap(1, 1);
 
-    bufferSurface->doTransaction([](auto &t, auto &sc) { t.setAlpha(sc, 0.0); });
+    bufferSurface->doTransaction([](auto& t, auto& sc) { t.setAlpha(sc, 0.0); });
 
     injectTap(11, 11);
     bgSurface->expectTap(1, 1);
@@ -715,7 +712,7 @@
     injectTap(11, 11);
     fgSurface->expectTap(1, 1);
 
-    fgSurface->doTransaction([](auto &t, auto &sc) { t.setAlpha(sc, 0.0); });
+    fgSurface->doTransaction([](auto& t, auto& sc) { t.setAlpha(sc, 0.0); });
 
     injectTap(11, 11);
     fgSurface->expectTap(1, 1);
@@ -732,7 +729,7 @@
     injectTap(11, 11);
     containerSurface->expectTap(1, 1);
 
-    containerSurface->doTransaction([](auto &t, auto &sc) { t.hide(sc); });
+    containerSurface->doTransaction([](auto& t, auto& sc) { t.hide(sc); });
 
     injectTap(11, 11);
     bgSurface->expectTap(1, 1);
@@ -772,19 +769,19 @@
 TEST_F(InputSurfacesTest, rotate_surface) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->showAt(10, 10);
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, 1, -1, 0); // 90 degrees
     });
     injectTap(8, 11);
     surface->expectTap(1, 2);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, -1, 0, 0, -1); // 180 degrees
     });
     injectTap(9, 8);
     surface->expectTap(1, 2);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, -1, 1, 0); // 270 degrees
     });
     injectTap(12, 9);
@@ -794,19 +791,19 @@
 TEST_F(InputSurfacesTest, rotate_surface_with_scale) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->showAt(10, 10);
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, 2, -4, 0); // 90 degrees
     });
     injectTap(2, 12);
     surface->expectTap(1, 2);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, -2, 0, 0, -4); // 180 degrees
     });
     injectTap(8, 2);
     surface->expectTap(1, 2);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, -2, 4, 0); // 270 degrees
     });
     injectTap(18, 8);
@@ -818,19 +815,19 @@
     surface->mInputInfo.surfaceInset = 5;
     surface->showAt(100, 100);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, 2, -4, 0); // 90 degrees
     });
     injectTap(40, 120);
     surface->expectTap(5, 10);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, -2, 0, 0, -4); // 180 degrees
     });
     injectTap(80, 40);
     surface->expectTap(5, 10);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, -2, 4, 0); // 270 degrees
     });
     injectTap(160, 80);
@@ -871,7 +868,7 @@
     nonTouchableSurface->showAt(0, 0);
     parentSurface->showAt(100, 100);
 
-    nonTouchableSurface->doTransaction([&](auto &t, auto &sc) {
+    nonTouchableSurface->doTransaction([&](auto& t, auto& sc) {
         t.setCrop(parentSurface->mSurfaceControl, Rect(0, 0, 50, 50));
         t.reparent(sc, parentSurface->mSurfaceControl);
     });
@@ -895,7 +892,7 @@
     nonTouchableSurface->showAt(0, 0);
     parentSurface->showAt(50, 50);
 
-    nonTouchableSurface->doTransaction([&](auto &t, auto &sc) {
+    nonTouchableSurface->doTransaction([&](auto& t, auto& sc) {
         t.setCrop(parentSurface->mSurfaceControl, Rect(0, 0, 50, 50));
         t.reparent(sc, parentSurface->mSurfaceControl);
     });
@@ -937,13 +934,11 @@
 TEST_F(InputSurfacesTest, strict_unobscured_input_unobscured_window) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->doTransaction(
-            [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
+            [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
     surface->showAt(100, 100);
 
     injectTap(101, 101);
-
-    EXPECT_NE(surface->consumeEvent(), nullptr);
-    EXPECT_NE(surface->consumeEvent(), nullptr);
+    surface->expectTap(1, 1);
 
     surface->requestFocus();
     surface->assertFocusChange(true);
@@ -953,16 +948,14 @@
 
 TEST_F(InputSurfacesTest, strict_unobscured_input_scaled_without_crop_window) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setDropInputMode(sc, gui::DropInputMode::OBSCURED);
         t.setMatrix(sc, 2.0, 0, 0, 2.0);
     });
     surface->showAt(100, 100);
 
     injectTap(101, 101);
-
-    EXPECT_NE(surface->consumeEvent(), nullptr);
-    EXPECT_NE(surface->consumeEvent(), nullptr);
+    surface->expectTap(.5, .5);
 
     surface->requestFocus();
     surface->assertFocusChange(true);
@@ -974,26 +967,26 @@
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->mInputInfo.ownerUid = gui::Uid{11111};
     surface->doTransaction(
-            [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
+            [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
     surface->showAt(100, 100);
     std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
     obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222};
     obscuringSurface->showAt(100, 100);
     injectTap(101, 101);
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->mInputInfo.ownerUid = gui::Uid{11111};
     surface->doTransaction(
-            [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
+            [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
     surface->showAt(100, 100);
     std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
     obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
@@ -1002,12 +995,12 @@
 
     injectTap(101, 101);
 
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) {
@@ -1016,7 +1009,7 @@
 
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->showAt(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setDropInputMode(sc, gui::DropInputMode::OBSCURED);
         t.reparent(sc, parentSurface->mSurfaceControl);
         t.setAlpha(parentSurface->mSurfaceControl, 0.9f);
@@ -1024,12 +1017,12 @@
 
     injectTap(101, 101);
 
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) {
@@ -1037,7 +1030,7 @@
     parentSurface->showAt(0, 0, Rect(0, 0, 300, 300));
 
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setDropInputMode(sc, gui::DropInputMode::OBSCURED);
         t.reparent(sc, parentSurface->mSurfaceControl);
         t.setCrop(parentSurface->mSurfaceControl, Rect(10, 10, 100, 100));
@@ -1046,12 +1039,12 @@
 
     injectTap(111, 111);
 
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) {
@@ -1071,17 +1064,16 @@
 TEST_F(InputSurfacesTest, drop_input_policy) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->doTransaction(
-            [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::ALL); });
+            [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::ALL); });
     surface->showAt(100, 100);
 
     injectTap(101, 101);
-
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) {
@@ -1104,7 +1096,7 @@
     std::unique_ptr<InputSurface> containerSurface =
             InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
     containerSurface->doTransaction(
-            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+            [&](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));
@@ -1116,7 +1108,7 @@
 
     // Does not receive events outside its crop
     injectTap(26, 26);
-    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+    containerSurface->assertNoEvent();
 }
 
 /**
@@ -1129,7 +1121,7 @@
     std::unique_ptr<InputSurface> containerSurface =
             InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
     containerSurface->doTransaction(
-            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+            [&](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));
@@ -1141,7 +1133,7 @@
 
     // Does not receive events outside parent bounds
     injectTap(31, 31);
-    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+    containerSurface->assertNoEvent();
 }
 
 /**
@@ -1167,7 +1159,7 @@
     // Does not receive events outside crop layer bounds
     injectTap(21, 21);
     injectTap(71, 71);
-    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+    containerSurface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) {
@@ -1181,19 +1173,19 @@
             InputSurface::makeContainerInputSurfaceNoInputChannel(mComposerClient, 100, 100);
     childContainerSurface->showAt(0, 0);
     childContainerSurface->doTransaction(
-            [&](auto &t, auto &sc) { t.reparent(sc, parent->mSurfaceControl); });
+            [&](auto& t, auto& sc) { t.reparent(sc, parent->mSurfaceControl); });
     injectTap(101, 101);
 
-    EXPECT_EQ(parent->consumeEvent(100), nullptr);
+    parent->assertNoEvent();
 }
 
 class MultiDisplayTests : public InputSurfacesTest {
 public:
     MultiDisplayTests() : InputSurfacesTest() { ProcessState::self()->startThreadPool(); }
+
     void TearDown() override {
-        for (auto &token : mVirtualDisplays) {
-            SurfaceComposerClient::destroyDisplay(token);
-        }
+        std::for_each(mVirtualDisplays.begin(), mVirtualDisplays.end(),
+                      SurfaceComposerClient::destroyVirtualDisplay);
         InputSurfacesTest::TearDown();
     }
 
@@ -1208,7 +1200,7 @@
 
         std::string name = "VirtualDisplay";
         name += std::to_string(mVirtualDisplays.size());
-        sp<IBinder> token = SurfaceComposerClient::createDisplay(String8(name.c_str()), isSecure);
+        sp<IBinder> token = SurfaceComposerClient::createVirtualDisplay(name, isSecure);
         SurfaceComposerClient::Transaction t;
         t.setDisplaySurface(token, producer);
         t.setDisplayFlags(token, receivesInput ? 0x01 /* DisplayDevice::eReceivesInput */ : 0);
@@ -1228,18 +1220,18 @@
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     // Do not create a display associated with the LayerStack.
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
+    surface->doTransaction([&](auto& t, auto& sc) { t.setLayerStack(sc, layerStack); });
     surface->showAt(100, 100);
 
     // Touches should be dropped if the layer is on an invalid display.
-    injectTapOnDisplay(101, 101, layerStack.id);
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    injectTapOnDisplay(101, 101, toDisplayId(layerStack));
+    surface->assertNoEvent();
 
     // However, we still let the window be focused and receive keys.
-    surface->requestFocus(layerStack.id);
+    surface->requestFocus(toDisplayId(layerStack));
     surface->assertFocusChange(true);
 
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack));
     surface->expectKey(AKEYCODE_V);
 }
 
@@ -1247,15 +1239,15 @@
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     createDisplay(1000, 1000, false /*isSecure*/, layerStack);
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
+    surface->doTransaction([&](auto& t, auto& sc) { t.setLayerStack(sc, layerStack); });
     surface->showAt(100, 100);
 
-    injectTapOnDisplay(101, 101, layerStack.id);
+    injectTapOnDisplay(101, 101, toDisplayId(layerStack));
     surface->expectTap(1, 1);
 
-    surface->requestFocus(layerStack.id);
+    surface->requestFocus(toDisplayId(layerStack));
     surface->assertFocusChange(true);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack));
     surface->expectKey(AKEYCODE_V);
 }
 
@@ -1263,20 +1255,20 @@
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     createDisplay(1000, 1000, false /*isSecure*/, layerStack);
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setFlags(sc, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
         t.setLayerStack(sc, layerStack);
     });
     surface->showAt(100, 100);
 
-    injectTapOnDisplay(101, 101, layerStack.id);
+    injectTapOnDisplay(101, 101, toDisplayId(layerStack));
 
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    surface->assertNoEvent();
 
-    surface->requestFocus(layerStack.id);
+    surface->requestFocus(toDisplayId(layerStack));
     surface->assertFocusChange(true);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
-    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+    injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack));
+    surface->assertNoEvent();
 }
 
 TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) {
@@ -1289,21 +1281,21 @@
     seteuid(AID_ROOT);
 
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setFlags(sc, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
         t.setLayerStack(sc, layerStack);
     });
     surface->showAt(100, 100);
 
-    injectTapOnDisplay(101, 101, layerStack.id);
-    EXPECT_NE(surface->consumeEvent(), nullptr);
-    EXPECT_NE(surface->consumeEvent(), nullptr);
+    injectTapOnDisplay(101, 101, toDisplayId(layerStack));
+    surface->expectTap(1, 1);
 
-    surface->requestFocus(layerStack.id);
+    surface->requestFocus(toDisplayId(layerStack));
     surface->assertFocusChange(true);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack));
 
     surface->expectKey(AKEYCODE_V);
 }
 
-} // namespace android::test
+} // namespace test
+} // namespace android
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
index 5fe22b0..04bfb28 100644
--- a/libs/gui/tests/FrameRateUtilsTest.cpp
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -34,6 +34,8 @@
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
     EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_GTE,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
     // Privileged APIs.
     EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index c6ea317..43cd0f8 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -173,7 +173,7 @@
         // Acquire and free 1+extraDiscardedBuffers buffer, check onBufferReleased is called.
         std::vector<BufferItem> releasedItems;
         releasedItems.resize(1+extraDiscardedBuffers);
-        for (int i = 0; i < releasedItems.size(); i++) {
+        for (size_t i = 0; i < releasedItems.size(); i++) {
             ASSERT_EQ(NO_ERROR, consumer->acquireBuffer(&releasedItems[i], 0));
             ASSERT_EQ(NO_ERROR, consumer->releaseBuffer(releasedItems[i].mSlot,
                     releasedItems[i].mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
@@ -197,7 +197,7 @@
             // Check onBufferDiscarded is called with correct buffer
             auto discardedBuffers = listener->getDiscardedBuffers();
             ASSERT_EQ(discardedBuffers.size(), releasedItems.size());
-            for (int i = 0; i < releasedItems.size(); i++) {
+            for (size_t i = 0; i < releasedItems.size(); i++) {
                 ASSERT_EQ(discardedBuffers[i], releasedItems[i].mGraphicBuffer);
             }
 
@@ -673,13 +673,14 @@
         return binder::Status::ok();
     }
 
-    binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/,
-                                 float /*requestedRefreshRate*/,
-                                 sp<IBinder>* /*outDisplay*/) override {
+    binder::Status createVirtualDisplay(const std::string& /*displayName*/, bool /*isSecure*/,
+                                        const std::string& /*uniqueId*/,
+                                        float /*requestedRefreshRate*/,
+                                        sp<IBinder>* /*outDisplay*/) override {
         return binder::Status::ok();
     }
 
-    binder::Status destroyDisplay(const sp<IBinder>& /*display*/) override {
+    binder::Status destroyVirtualDisplay(const sp<IBinder>& /*displayToken*/) override {
         return binder::Status::ok();
     }
 
@@ -791,6 +792,10 @@
         return binder::Status::ok();
     }
 
+    binder::Status captureLayersSync(const LayerCaptureArgs&, ScreenCaptureResults*) override {
+        return binder::Status::ok();
+    }
+
     binder::Status captureLayers(const LayerCaptureArgs&,
                                  const sp<IScreenCaptureListener>&) override {
         return binder::Status::ok();
@@ -811,10 +816,6 @@
         return binder::Status::ok();
     }
 
-    binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* /*outLayers*/) override {
-        return binder::Status::ok();
-    }
-
     binder::Status getCompositionPreference(gui::CompositionPreference* /*outPref*/) override {
         return binder::Status::ok();
     }
@@ -984,6 +985,8 @@
         return binder::Status::ok();
     }
 
+    binder::Status notifyShutdown() override { return binder::Status::ok(); }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index 5eb5d3b..ce22082 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -64,7 +64,7 @@
     i.ownerUid = gui::Uid{24};
     i.packageName = "com.example.package";
     i.inputConfig = WindowInfo::InputConfig::NOT_FOCUSABLE;
-    i.displayId = 34;
+    i.displayId = ui::LogicalDisplayId{34};
     i.replaceTouchableRegionWithCrop = true;
     i.touchableRegionCropHandle = touchableRegionCropHandle;
     i.applicationInfo.name = "ApplicationFooBar";
diff --git a/libs/input/AccelerationCurve.cpp b/libs/input/AccelerationCurve.cpp
new file mode 100644
index 0000000..0a92a71
--- /dev/null
+++ b/libs/input/AccelerationCurve.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 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 <input/AccelerationCurve.h>
+
+#include <array>
+#include <limits>
+
+#include <log/log_main.h>
+
+#define LOG_TAG "AccelerationCurve"
+
+namespace android {
+
+namespace {
+
+// The last segment must have an infinite maximum speed, so that all speeds are covered.
+constexpr std::array<AccelerationCurveSegment, 4> kSegments = {{
+        {32.002, 3.19, 0},
+        {52.83, 4.79, -51.254},
+        {119.124, 7.28, -182.737},
+        {std::numeric_limits<double>::infinity(), 15.04, -1107.556},
+}};
+
+static_assert(kSegments.back().maxPointerSpeedMmPerS == std::numeric_limits<double>::infinity());
+
+constexpr std::array<double, 15> kSensitivityFactors = {1,  2,  4,  6,  7,  8,  9, 10,
+                                                        11, 12, 13, 14, 16, 18, 20};
+
+} // namespace
+
+std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
+        int32_t sensitivity) {
+    LOG_ALWAYS_FATAL_IF(sensitivity < -7 || sensitivity > 7, "Invalid pointer sensitivity value");
+    std::vector<AccelerationCurveSegment> output;
+    output.reserve(kSegments.size());
+
+    // The curves we want to produce for different sensitivity values are actually the same curve,
+    // just scaled in the Y (gain) axis by a sensitivity factor and a couple of constants.
+    double commonFactor = 0.64 * kSensitivityFactors[sensitivity + 7] / 10;
+    for (AccelerationCurveSegment seg : kSegments) {
+        output.push_back(AccelerationCurveSegment{seg.maxPointerSpeedMmPerS,
+                                                  commonFactor * seg.baseGain,
+                                                  commonFactor * seg.reciprocal});
+    }
+
+    return output;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 20b3538..8f44b3a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,6 +30,7 @@
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
         "android/os/InputConfig.aidl",
+        "android/os/PointerIconType.aidl",
     ],
 }
 
@@ -88,11 +89,6 @@
 
     bindgen_flags: [
         "--verbose",
-        "--allowlist-var=AMOTION_EVENT_FLAG_CANCELED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT",
-        "--allowlist-var=AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE",
         "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
         "--allowlist-var=AMOTION_EVENT_ACTION_UP",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
@@ -135,6 +131,29 @@
     ],
 }
 
+cc_library_static {
+    name: "iinputflinger_aidl_lib_static",
+    host_supported: true,
+    srcs: [
+        "android/os/IInputFlinger.aidl",
+        "android/os/InputChannelCore.aidl",
+    ],
+    shared_libs: [
+        "libbinder",
+    ],
+    whole_static_libs: [
+        "libgui_window_info_static",
+    ],
+    aidl: {
+        export_aidl_headers: true,
+        local_include_dirs: ["."],
+        include_dirs: [
+            "frameworks/native/libs/gui",
+            "frameworks/native/libs/input",
+        ],
+    },
+}
+
 // Contains methods to help access C++ code from rust
 cc_library_static {
     name: "libinput_from_rust_to_cpp",
@@ -179,8 +198,10 @@
         "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
     ],
     srcs: [
-        "android/os/IInputFlinger.aidl",
+        "AccelerationCurve.cpp",
         "Input.cpp",
+        "InputConsumer.cpp",
+        "InputConsumerNoResampling.cpp",
         "InputDevice.cpp",
         "InputEventLabels.cpp",
         "InputTransport.cpp",
@@ -220,6 +241,7 @@
     shared_libs: [
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "liblog",
         "libPlatformProperties",
@@ -241,7 +263,6 @@
 
     static_libs: [
         "inputconstants-cpp",
-        "libgui_window_info_static",
         "libui-types",
         "libtflite_static",
         "libkernelconfigs",
@@ -250,10 +271,10 @@
     whole_static_libs: [
         "com.android.input.flags-aconfig-cc",
         "libinput_rust_ffi",
+        "iinputflinger_aidl_lib_static",
     ],
 
     export_static_lib_headers: [
-        "libgui_window_info_static",
         "libui-types",
     ],
 
@@ -264,21 +285,14 @@
 
     target: {
         android: {
-            export_shared_lib_headers: ["libbinder"],
-
-            shared_libs: [
-                "libutils",
-                "libbinder",
-                // Stats logging library and its dependencies.
-                "libstatslog_libinput",
-                "libstatsbootstrap",
-                "android.os.statsbootstrap_aidl-cpp",
-            ],
-
             required: [
                 "motion_predictor_model_prebuilt",
                 "motion_predictor_model_config",
             ],
+            static_libs: [
+                "libstatslog_libinput",
+                "libstatssocket_lazy",
+            ],
         },
         host: {
             include_dirs: [
@@ -287,37 +301,32 @@
             ],
         },
     },
-
-    aidl: {
-        local_include_dirs: ["."],
-        export_aidl_headers: true,
-        include_dirs: [
-            "frameworks/native/libs/gui",
-        ],
-    },
 }
 
-// Use bootstrap version of stats logging library.
-// libinput is a bootstrap process (starts early in the boot process), and thus can't use the normal
-// `libstatslog` because that requires `libstatssocket`, which is only available later in the boot.
-cc_library {
+cc_library_static {
     name: "libstatslog_libinput",
     generated_sources: ["statslog_libinput.cpp"],
     generated_headers: ["statslog_libinput.h"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     export_generated_headers: ["statslog_libinput.h"],
     shared_libs: [
-        "libbinder",
-        "libstatsbootstrap",
+        "libcutils",
+        "liblog",
         "libutils",
-        "android.os.statsbootstrap_aidl-cpp",
+    ],
+    static_libs: [
+        "libstatssocket_lazy",
     ],
 }
 
 genrule {
     name: "statslog_libinput.h",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h --module libinput" +
-        " --namespace android,stats,libinput --bootstrap",
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h " +
+        "--module libinput --namespace android,libinput",
     out: [
         "statslog_libinput.h",
     ],
@@ -326,9 +335,9 @@
 genrule {
     name: "statslog_libinput.cpp",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp --module libinput" +
-        " --namespace android,stats,libinput --importHeader statslog_libinput.h" +
-        " --bootstrap",
+    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp " +
+        "--module libinput --namespace android,libinput " +
+        "--importHeader statslog_libinput.h",
     out: [
         "statslog_libinput.cpp",
     ],
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index bd5b67b..ee121d5 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -27,15 +27,12 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <cutils/compiler.h>
-#include <gui/constants.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
 
-#ifdef __linux__
 #include <binder/Parcel.h>
-#endif
 #if defined(__ANDROID__)
 #include <sys/random.h>
 #endif
@@ -60,6 +57,45 @@
     return !isFromSource(source, AINPUT_SOURCE_CLASS_POINTER);
 }
 
+int32_t resolveActionForSplitMotionEvent(
+        int32_t action, int32_t flags, const std::vector<PointerProperties>& pointerProperties,
+        const std::vector<PointerProperties>& splitPointerProperties) {
+    LOG_ALWAYS_FATAL_IF(splitPointerProperties.empty());
+    const auto maskedAction = MotionEvent::getActionMasked(action);
+    if (maskedAction != AMOTION_EVENT_ACTION_POINTER_DOWN &&
+        maskedAction != AMOTION_EVENT_ACTION_POINTER_UP) {
+        // The action is unaffected by splitting this motion event.
+        return action;
+    }
+    const auto actionIndex = MotionEvent::getActionIndex(action);
+    if (CC_UNLIKELY(actionIndex >= pointerProperties.size())) {
+        LOG(FATAL) << "Action index is out of bounds, index: " << actionIndex;
+    }
+
+    const auto affectedPointerId = pointerProperties[actionIndex].id;
+    std::optional<uint32_t> splitActionIndex;
+    for (uint32_t i = 0; i < splitPointerProperties.size(); i++) {
+        if (affectedPointerId == splitPointerProperties[i].id) {
+            splitActionIndex = i;
+            break;
+        }
+    }
+    if (!splitActionIndex.has_value()) {
+        // The affected pointer is not part of the split motion event.
+        return AMOTION_EVENT_ACTION_MOVE;
+    }
+
+    if (splitPointerProperties.size() > 1) {
+        return maskedAction | (*splitActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+    }
+
+    if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+        return ((flags & AMOTION_EVENT_FLAG_CANCELED) != 0) ? AMOTION_EVENT_ACTION_CANCEL
+                                                            : AMOTION_EVENT_ACTION_UP;
+    }
+    return AMOTION_EVENT_ACTION_DOWN;
+}
+
 } // namespace
 
 const char* motionClassificationToString(MotionClassification classification) {
@@ -254,8 +290,8 @@
             event.getButtonState()};
 }
 
-void InputEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
-                            std::array<uint8_t, 32> hmac) {
+void InputEvent::initialize(int32_t id, int32_t deviceId, uint32_t source,
+                            ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac) {
     mId = id;
     mDeviceId = deviceId;
     mSource = source;
@@ -317,10 +353,11 @@
     return InputEventLookup::getKeyCodeByLabel(label);
 }
 
-void KeyEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
-                          std::array<uint8_t, 32> hmac, int32_t action, int32_t flags,
-                          int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount,
-                          nsecs_t downTime, nsecs_t eventTime) {
+void KeyEvent::initialize(int32_t id, int32_t deviceId, uint32_t source,
+                          ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac,
+                          int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+                          int32_t metaState, int32_t repeatCount, nsecs_t downTime,
+                          nsecs_t eventTime) {
     InputEvent::initialize(id, deviceId, source, displayId, hmac);
     mAction = action;
     mFlags = flags;
@@ -374,11 +411,16 @@
     out << ", deviceId=" << event.getDeviceId();
     out << ", source=" << inputEventSourceToString(event.getSource());
     out << ", displayId=" << event.getDisplayId();
-    out << ", eventId=" << event.getId();
+    out << ", eventId=0x" << std::hex << event.getId() << std::dec;
     out << "}";
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const PointerProperties& properties) {
+    out << "Pointer(id=" << properties.id << ", " << ftl::enum_string(properties.toolType) << ")";
+    return out;
+}
+
 // --- PointerCoords ---
 
 float PointerCoords::getAxisValue(int32_t axis) const {
@@ -439,7 +481,6 @@
     scaleAxisValue(*this, AMOTION_EVENT_AXIS_RELATIVE_Y, windowYScale);
 }
 
-#ifdef __linux__
 status_t PointerCoords::readFromParcel(Parcel* parcel) {
     bits = parcel->readInt64();
 
@@ -467,7 +508,6 @@
     parcel->writeBool(isResampled);
     return OK;
 }
-#endif
 
 void PointerCoords::tooManyAxes(int axis) {
     ALOGW("Could not set value for axis %d because the PointerCoords structure is full and "
@@ -512,14 +552,15 @@
 
 // --- MotionEvent ---
 
-void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
-                             std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton,
-                             int32_t flags, int32_t edgeFlags, int32_t metaState,
-                             int32_t buttonState, MotionClassification classification,
-                             const ui::Transform& transform, float xPrecision, float yPrecision,
-                             float rawXCursorPosition, float rawYCursorPosition,
-                             const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
-                             size_t pointerCount, const PointerProperties* pointerProperties,
+void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source,
+                             ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac,
+                             int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
+                             int32_t metaState, int32_t buttonState,
+                             MotionClassification classification, const ui::Transform& transform,
+                             float xPrecision, float yPrecision, float rawXCursorPosition,
+                             float rawYCursorPosition, const ui::Transform& rawTransform,
+                             nsecs_t downTime, nsecs_t eventTime, size_t pointerCount,
+                             const PointerProperties* pointerProperties,
                              const PointerCoords* pointerCoords) {
     InputEvent::initialize(id, deviceId, source, displayId, hmac);
     mAction = action;
@@ -579,6 +620,28 @@
     }
 }
 
+void MotionEvent::splitFrom(const android::MotionEvent& other,
+                            std::bitset<MAX_POINTER_ID + 1> splitPointerIds, int32_t newEventId) {
+    // TODO(b/327503168): The down time should be a parameter to the split function, because only
+    //   the caller can know when the first event went down on the target.
+    const nsecs_t splitDownTime = other.mDownTime;
+
+    auto [action, pointerProperties, pointerCoords] =
+            split(other.getAction(), other.getFlags(), other.getHistorySize(),
+                  other.mPointerProperties, other.mSamplePointerCoords, splitPointerIds);
+
+    // Initialize the event with zero pointers, and manually set the split pointers.
+    initialize(newEventId, other.mDeviceId, other.mSource, other.mDisplayId, /*hmac=*/{}, action,
+               other.mActionButton, other.mFlags, other.mEdgeFlags, other.mMetaState,
+               other.mButtonState, other.mClassification, other.mTransform, other.mXPrecision,
+               other.mYPrecision, other.mRawXCursorPosition, other.mRawYCursorPosition,
+               other.mRawTransform, splitDownTime, other.getEventTime(), /*pointerCount=*/0,
+               pointerProperties.data(), pointerCoords.data());
+    mPointerProperties = std::move(pointerProperties);
+    mSamplePointerCoords = std::move(pointerCoords);
+    mSampleEventTimes = other.mSampleEventTimes;
+}
+
 void MotionEvent::addSample(
         int64_t eventTime,
         const PointerCoords* pointerCoords) {
@@ -685,6 +748,18 @@
     mTransform.set(currXOffset + xOffset, currYOffset + yOffset);
 }
 
+float MotionEvent::getRawXOffset() const {
+    // This is equivalent to the x-coordinate of the point that the origin of the raw coordinate
+    // space maps to.
+    return (mTransform * mRawTransform.inverse()).tx();
+}
+
+float MotionEvent::getRawYOffset() const {
+    // This is equivalent to the y-coordinate of the point that the origin of the raw coordinate
+    // space maps to.
+    return (mTransform * mRawTransform.inverse()).ty();
+}
+
 void MotionEvent::scale(float globalScaleFactor) {
     mTransform.set(mTransform.tx() * globalScaleFactor, mTransform.ty() * globalScaleFactor);
     mRawTransform.set(mRawTransform.tx() * globalScaleFactor,
@@ -722,7 +797,6 @@
     }
 }
 
-#ifdef __linux__
 static status_t readFromParcel(ui::Transform& transform, const Parcel& parcel) {
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
     status_t status = parcel.readFloat(&dsdx);
@@ -757,7 +831,7 @@
     mId = parcel->readInt32();
     mDeviceId = parcel->readInt32();
     mSource = parcel->readUint32();
-    mDisplayId = parcel->readInt32();
+    mDisplayId = ui::LogicalDisplayId{parcel->readInt32()};
     std::vector<uint8_t> hmac;
     status_t result = parcel->readByteVector(&hmac);
     if (result != OK || hmac.size() != 32) {
@@ -825,7 +899,7 @@
     parcel->writeInt32(mId);
     parcel->writeInt32(mDeviceId);
     parcel->writeUint32(mSource);
-    parcel->writeInt32(mDisplayId);
+    parcel->writeInt32(mDisplayId.val());
     std::vector<uint8_t> hmac(mHmac.begin(), mHmac.end());
     parcel->writeByteVector(hmac);
     parcel->writeInt32(mAction);
@@ -869,7 +943,6 @@
     }
     return OK;
 }
-#endif
 
 bool MotionEvent::isTouchEvent(uint32_t source, int32_t action) {
     if (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER)) {
@@ -929,6 +1002,46 @@
     return android::base::StringPrintf("%" PRId32, action);
 }
 
+std::tuple<int32_t, std::vector<PointerProperties>, std::vector<PointerCoords>> MotionEvent::split(
+        int32_t action, int32_t flags, int32_t historySize,
+        const std::vector<PointerProperties>& pointerProperties,
+        const std::vector<PointerCoords>& pointerCoords,
+        std::bitset<MAX_POINTER_ID + 1> splitPointerIds) {
+    LOG_ALWAYS_FATAL_IF(!splitPointerIds.any());
+    const auto pointerCount = pointerProperties.size();
+    LOG_ALWAYS_FATAL_IF(pointerCoords.size() != (pointerCount * (historySize + 1)));
+    const auto splitCount = splitPointerIds.count();
+
+    std::vector<PointerProperties> splitPointerProperties;
+    std::vector<PointerCoords> splitPointerCoords;
+
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        if (splitPointerIds.test(pointerProperties[i].id)) {
+            splitPointerProperties.emplace_back(pointerProperties[i]);
+        }
+    }
+    for (uint32_t i = 0; i < pointerCoords.size(); i++) {
+        if (splitPointerIds.test(pointerProperties[i % pointerCount].id)) {
+            splitPointerCoords.emplace_back(pointerCoords[i]);
+        }
+    }
+    LOG_ALWAYS_FATAL_IF(splitPointerCoords.size() !=
+                        (splitPointerProperties.size() * (historySize + 1)));
+
+    if (CC_UNLIKELY(splitPointerProperties.size() != splitCount)) {
+        // TODO(b/329107108): Promote this to a fatal check once bugs in the caller are resolved.
+        LOG(ERROR) << "Cannot split MotionEvent: Requested splitting " << splitCount
+                   << " pointers from the original event, but the original event only contained "
+                   << splitPointerProperties.size() << " of those pointers.";
+    }
+
+    // TODO(b/327503168): Verify the splitDownTime here once it is used correctly.
+
+    const auto splitAction = resolveActionForSplitMotionEvent(action, flags, pointerProperties,
+                                                              splitPointerProperties);
+    return {splitAction, splitPointerProperties, splitPointerCoords};
+}
+
 // Apply the given transformation to the point without checking whether the entire transform
 // should be disregarded altogether for the provided source.
 static inline vec2 calculateTransformedXYUnchecked(uint32_t source, const ui::Transform& transform,
@@ -1002,6 +1115,33 @@
     return out;
 }
 
+bool MotionEvent::operator==(const android::MotionEvent& o) const {
+    // We use NaN values to represent invalid cursor positions. Since NaN values are not equal
+    // to themselves according to IEEE 754, we cannot use the default equality operator to compare
+    // MotionEvents. Therefore we define a custom equality operator with special handling for NaNs.
+    // clang-format off
+    return InputEvent::operator==(static_cast<const InputEvent&>(o)) &&
+            mAction == o.mAction &&
+            mActionButton == o.mActionButton &&
+            mFlags == o.mFlags &&
+            mEdgeFlags == o.mEdgeFlags &&
+            mMetaState == o.mMetaState &&
+            mButtonState == o.mButtonState &&
+            mClassification == o.mClassification &&
+            mTransform == o.mTransform &&
+            mXPrecision == o.mXPrecision &&
+            mYPrecision == o.mYPrecision &&
+            ((std::isnan(mRawXCursorPosition) && std::isnan(o.mRawXCursorPosition)) ||
+                mRawXCursorPosition == o.mRawXCursorPosition) &&
+            ((std::isnan(mRawYCursorPosition) && std::isnan(o.mRawYCursorPosition)) ||
+                mRawYCursorPosition == o.mRawYCursorPosition) &&
+            mRawTransform == o.mRawTransform && mDownTime == o.mDownTime &&
+            mPointerProperties == o.mPointerProperties &&
+            mSampleEventTimes == o.mSampleEventTimes &&
+            mSamplePointerCoords == o.mSamplePointerCoords;
+    // clang-format on
+}
+
 std::ostream& operator<<(std::ostream& out, const MotionEvent& event) {
     out << "MotionEvent { action=" << MotionEvent::actionToString(event.getAction());
     if (event.getActionButton() != 0) {
@@ -1032,6 +1172,9 @@
     if (event.getMetaState() != 0) {
         out << ", metaState=" << event.getMetaState();
     }
+    if (event.getFlags() != 0) {
+        out << ", flags=0x" << std::hex << event.getFlags() << std::dec;
+    }
     if (event.getEdgeFlags() != 0) {
         out << ", edgeFlags=" << event.getEdgeFlags();
     }
@@ -1046,7 +1189,7 @@
     out << ", deviceId=" << event.getDeviceId();
     out << ", source=" << inputEventSourceToString(event.getSource());
     out << ", displayId=" << event.getDisplayId();
-    out << ", eventId=" << event.getId();
+    out << ", eventId=0x" << std::hex << event.getId() << std::dec;
     out << "}";
     return out;
 }
@@ -1055,7 +1198,7 @@
 
 void FocusEvent::initialize(int32_t id, bool hasFocus) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
-                           ADISPLAY_ID_NONE, INVALID_HMAC);
+                           ui::LogicalDisplayId::INVALID, INVALID_HMAC);
     mHasFocus = hasFocus;
 }
 
@@ -1068,7 +1211,7 @@
 
 void CaptureEvent::initialize(int32_t id, bool pointerCaptureEnabled) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
-                           ADISPLAY_ID_NONE, INVALID_HMAC);
+                           ui::LogicalDisplayId::INVALID, INVALID_HMAC);
     mPointerCaptureEnabled = pointerCaptureEnabled;
 }
 
@@ -1081,7 +1224,7 @@
 
 void DragEvent::initialize(int32_t id, float x, float y, bool isExiting) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
-                           ADISPLAY_ID_NONE, INVALID_HMAC);
+                           ui::LogicalDisplayId::INVALID, INVALID_HMAC);
     mIsExiting = isExiting;
     mX = x;
     mY = y;
@@ -1098,7 +1241,7 @@
 
 void TouchModeEvent::initialize(int32_t id, bool isInTouchMode) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
-                           ADISPLAY_ID_NONE, INVALID_HMAC);
+                           ui::LogicalDisplayId::INVALID, INVALID_HMAC);
     mIsInTouchMode = isInTouchMode;
 }
 
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
new file mode 100644
index 0000000..abc0392
--- /dev/null
+++ b/libs/input/InputConsumer.cpp
@@ -0,0 +1,954 @@
+/**
+ * Copyright 2024 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 <cstdint>
+#define LOG_TAG "InputTransport"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <math.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <binder/Parcel.h>
+#include <cutils/properties.h>
+#include <ftl/enum.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <com_android_input_flags.h>
+#include <input/InputConsumer.h>
+#include <input/PrintTools.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
+
+namespace android {
+
+namespace {
+
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+
+const bool DEBUG_TRANSPORT_CONSUMER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+        android::base::GetBoolProperty("ro.debuggable", false);
+#else
+        true;
+#endif
+
+/**
+ * Log debug messages about touch event resampling.
+ *
+ * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
+ */
+bool debugResampling() {
+    if (!IS_DEBUGGABLE_BUILD) {
+        static const bool DEBUG_TRANSPORT_RESAMPLING =
+                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
+                                          ANDROID_LOG_INFO);
+        return DEBUG_TRANSPORT_RESAMPLING;
+    }
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+}
+
+void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
+                     ui::LogicalDisplayId{msg.body.key.displayId}, msg.body.key.hmac,
+                     msg.body.key.action, msg.body.key.flags, msg.body.key.keyCode,
+                     msg.body.key.scanCode, msg.body.key.metaState, msg.body.key.repeatCount,
+                     msg.body.key.downTime, msg.body.key.eventTime);
+}
+
+void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+}
+
+void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+}
+
+void initializeDragEvent(DragEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
+                     msg.body.drag.isExiting);
+}
+
+void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) {
+    uint32_t pointerCount = msg.body.motion.pointerCount;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i] = msg.body.motion.pointers[i].properties;
+        pointerCoords[i] = msg.body.motion.pointers[i].coords;
+    }
+
+    ui::Transform transform;
+    transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
+                   msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
+    ui::Transform displayTransform;
+    displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
+                          msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
+                          0, 0, 1});
+    event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
+                     ui::LogicalDisplayId{msg.body.motion.displayId}, msg.body.motion.hmac,
+                     msg.body.motion.action, msg.body.motion.actionButton, msg.body.motion.flags,
+                     msg.body.motion.edgeFlags, msg.body.motion.metaState,
+                     msg.body.motion.buttonState, msg.body.motion.classification, transform,
+                     msg.body.motion.xPrecision, msg.body.motion.yPrecision,
+                     msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition,
+                     displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime,
+                     pointerCount, pointerProperties, pointerCoords);
+}
+
+void addSample(MotionEvent& event, const InputMessage& msg) {
+    uint32_t pointerCount = msg.body.motion.pointerCount;
+    PointerCoords pointerCoords[pointerCount];
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerCoords[i] = msg.body.motion.pointers[i].coords;
+    }
+
+    event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
+    event.addSample(msg.body.motion.eventTime, pointerCoords);
+}
+
+void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+}
+
+// Nanoseconds per milliseconds.
+constexpr nsecs_t NANOS_PER_MS = 1000000;
+
+// Latency added during resampling.  A few milliseconds doesn't hurt much but
+// reduces the impact of mispredicted touch positions.
+const std::chrono::duration RESAMPLE_LATENCY = 5ms;
+
+// Minimum time difference between consecutive samples before attempting to resample.
+const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
+
+// Maximum time difference between consecutive samples before attempting to resample
+// by extrapolation.
+const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS;
+
+// Maximum time to predict forward from the last known state, to avoid predicting too
+// far into the future.  This time is further bounded by 50% of the last time delta.
+const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
+
+/**
+ * System property for enabling / disabling touch resampling.
+ * Resampling extrapolates / interpolates the reported touch event coordinates to better
+ * align them to the VSYNC signal, thus resulting in smoother scrolling performance.
+ * Resampling is not needed (and should be disabled) on hardware that already
+ * has touch events triggered by VSYNC.
+ * Set to "1" to enable resampling (default).
+ * Set to "0" to disable resampling.
+ * Resampling is enabled by default.
+ */
+const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
+
+inline float lerp(float a, float b, float alpha) {
+    return a + alpha * (b - a);
+}
+
+inline bool isPointerEvent(int32_t source) {
+    return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
+}
+
+bool shouldResampleTool(ToolType toolType) {
+    return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN;
+}
+
+} // namespace
+
+using android::base::Result;
+using android::base::StringPrintf;
+
+// --- InputConsumer ---
+
+InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
+      : InputConsumer(channel, isTouchResamplingEnabled()) {}
+
+InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
+                             bool enableTouchResampling)
+      : mResampleTouch(enableTouchResampling),
+        mChannel(channel),
+        mProcessingTraceTag(StringPrintf("InputConsumer processing on %s (%p)",
+                                         mChannel->getName().c_str(), this)),
+        mLifetimeTraceTag(StringPrintf("InputConsumer lifetime on %s (%p)",
+                                       mChannel->getName().c_str(), this)),
+        mLifetimeTraceCookie(
+                static_cast<int32_t>(reinterpret_cast<std::uintptr_t>(this) & 0xFFFFFFFF)),
+        mMsgDeferred(false) {
+    ATRACE_ASYNC_BEGIN(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie);
+}
+
+InputConsumer::~InputConsumer() {
+    ATRACE_ASYNC_END(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie);
+}
+
+bool InputConsumer::isTouchResamplingEnabled() {
+    return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
+}
+
+status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
+                                nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
+             mChannel->getName().c_str(), toString(consumeBatches), frameTime);
+
+    *outSeq = 0;
+    *outEvent = nullptr;
+
+    // Fetch the next input message.
+    // Loop until an event can be returned or no additional events are received.
+    while (!*outEvent) {
+        if (mMsgDeferred) {
+            // mMsg contains a valid input message from the previous call to consume
+            // that has not yet been processed.
+            mMsgDeferred = false;
+        } else {
+            // Receive a fresh message.
+            status_t result = mChannel->receiveMessage(&mMsg);
+            if (result == OK) {
+                const auto [_, inserted] =
+                        mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+                LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+                                    mMsg.header.seq);
+
+                // Trace the event processing timeline - event was just read from the socket
+                ATRACE_ASYNC_BEGIN(mProcessingTraceTag.c_str(), /*cookie=*/mMsg.header.seq);
+            }
+            if (result) {
+                // Consume the next batched event unless batches are being held for later.
+                if (consumeBatches || result != WOULD_BLOCK) {
+                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
+                    if (*outEvent) {
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ consumed batch event, seq=%u",
+                                 mChannel->getName().c_str(), *outSeq);
+                        break;
+                    }
+                }
+                return result;
+            }
+        }
+
+        switch (mMsg.header.type) {
+            case InputMessage::Type::KEY: {
+                KeyEvent* keyEvent = factory->createKeyEvent();
+                if (!keyEvent) return NO_MEMORY;
+
+                initializeKeyEvent(*keyEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = keyEvent;
+                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                         "channel '%s' consumer ~ consumed key event, seq=%u",
+                         mChannel->getName().c_str(), *outSeq);
+                break;
+            }
+
+            case InputMessage::Type::MOTION: {
+                ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
+                if (batchIndex >= 0) {
+                    Batch& batch = mBatches[batchIndex];
+                    if (canAddSample(batch, &mMsg)) {
+                        batch.samples.push_back(mMsg);
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ appended to batch event",
+                                 mChannel->getName().c_str());
+                        break;
+                    } else if (isPointerEvent(mMsg.body.motion.source) &&
+                               mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
+                        // No need to process events that we are going to cancel anyways
+                        const size_t count = batch.samples.size();
+                        for (size_t i = 0; i < count; i++) {
+                            const InputMessage& msg = batch.samples[i];
+                            sendFinishedSignal(msg.header.seq, false);
+                        }
+                        batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
+                        mBatches.erase(mBatches.begin() + batchIndex);
+                    } else {
+                        // We cannot append to the batch in progress, so we need to consume
+                        // the previous batch right now and defer the new message until later.
+                        mMsgDeferred = true;
+                        status_t result = consumeSamples(factory, batch, batch.samples.size(),
+                                                         outSeq, outEvent);
+                        mBatches.erase(mBatches.begin() + batchIndex);
+                        if (result) {
+                            return result;
+                        }
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ consumed batch event and "
+                                 "deferred current event, seq=%u",
+                                 mChannel->getName().c_str(), *outSeq);
+                        break;
+                    }
+                }
+
+                // Start a new batch if needed.
+                if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
+                    mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+                    Batch batch;
+                    batch.samples.push_back(mMsg);
+                    mBatches.push_back(batch);
+                    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                             "channel '%s' consumer ~ started batch event",
+                             mChannel->getName().c_str());
+                    break;
+                }
+
+                MotionEvent* motionEvent = factory->createMotionEvent();
+                if (!motionEvent) return NO_MEMORY;
+
+                updateTouchState(mMsg);
+                initializeMotionEvent(*motionEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = motionEvent;
+
+                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                         "channel '%s' consumer ~ consumed motion event, seq=%u",
+                         mChannel->getName().c_str(), *outSeq);
+                break;
+            }
+
+            case InputMessage::Type::FINISHED:
+            case InputMessage::Type::TIMELINE: {
+                LOG(FATAL) << "Consumed a " << ftl::enum_string(mMsg.header.type)
+                           << " message, which should never be seen by "
+                              "InputConsumer on "
+                           << mChannel->getName();
+                break;
+            }
+
+            case InputMessage::Type::FOCUS: {
+                FocusEvent* focusEvent = factory->createFocusEvent();
+                if (!focusEvent) return NO_MEMORY;
+
+                initializeFocusEvent(*focusEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = focusEvent;
+                break;
+            }
+
+            case InputMessage::Type::CAPTURE: {
+                CaptureEvent* captureEvent = factory->createCaptureEvent();
+                if (!captureEvent) return NO_MEMORY;
+
+                initializeCaptureEvent(*captureEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = captureEvent;
+                break;
+            }
+
+            case InputMessage::Type::DRAG: {
+                DragEvent* dragEvent = factory->createDragEvent();
+                if (!dragEvent) return NO_MEMORY;
+
+                initializeDragEvent(*dragEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = dragEvent;
+                break;
+            }
+
+            case InputMessage::Type::TOUCH_MODE: {
+                TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
+                if (!touchModeEvent) return NO_MEMORY;
+
+                initializeTouchModeEvent(*touchModeEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = touchModeEvent;
+                break;
+            }
+        }
+    }
+    return OK;
+}
+
+status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime,
+                                     uint32_t* outSeq, InputEvent** outEvent) {
+    status_t result;
+    for (size_t i = mBatches.size(); i > 0;) {
+        i--;
+        Batch& batch = mBatches[i];
+        if (frameTime < 0) {
+            result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent);
+            mBatches.erase(mBatches.begin() + i);
+            return result;
+        }
+
+        nsecs_t sampleTime = frameTime;
+        if (mResampleTouch) {
+            sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count();
+        }
+        ssize_t split = findSampleNoLaterThan(batch, sampleTime);
+        if (split < 0) {
+            continue;
+        }
+
+        result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
+        const InputMessage* next;
+        if (batch.samples.empty()) {
+            mBatches.erase(mBatches.begin() + i);
+            next = nullptr;
+        } else {
+            next = &batch.samples[0];
+        }
+        if (!result && mResampleTouch) {
+            resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
+        }
+        return result;
+    }
+
+    return WOULD_BLOCK;
+}
+
+status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, Batch& batch,
+                                       size_t count, uint32_t* outSeq, InputEvent** outEvent) {
+    MotionEvent* motionEvent = factory->createMotionEvent();
+    if (!motionEvent) return NO_MEMORY;
+
+    uint32_t chain = 0;
+    for (size_t i = 0; i < count; i++) {
+        InputMessage& msg = batch.samples[i];
+        updateTouchState(msg);
+        if (i) {
+            SeqChain seqChain;
+            seqChain.seq = msg.header.seq;
+            seqChain.chain = chain;
+            mSeqChains.push_back(seqChain);
+            addSample(*motionEvent, msg);
+        } else {
+            initializeMotionEvent(*motionEvent, msg);
+        }
+        chain = msg.header.seq;
+    }
+    batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
+
+    *outSeq = chain;
+    *outEvent = motionEvent;
+    return OK;
+}
+
+void InputConsumer::updateTouchState(InputMessage& msg) {
+    if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) {
+        return;
+    }
+
+    int32_t deviceId = msg.body.motion.deviceId;
+    int32_t source = msg.body.motion.source;
+
+    // Update the touch state history to incorporate the new input message.
+    // If the message is in the past relative to the most recently produced resampled
+    // touch, then use the resampled time and coordinates instead.
+    switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) {
+        case AMOTION_EVENT_ACTION_DOWN: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index < 0) {
+                mTouchStates.push_back({});
+                index = mTouchStates.size() - 1;
+            }
+            TouchState& touchState = mTouchStates[index];
+            touchState.initialize(deviceId, source);
+            touchState.addHistory(msg);
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_MOVE: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                touchState.addHistory(msg);
+                rewriteMessage(touchState, msg);
+            }
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
+                rewriteMessage(touchState, msg);
+            }
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_POINTER_UP: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                rewriteMessage(touchState, msg);
+                touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
+            }
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_SCROLL: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                rewriteMessage(touchState, msg);
+            }
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_UP:
+        case AMOTION_EVENT_ACTION_CANCEL: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                rewriteMessage(touchState, msg);
+                mTouchStates.erase(mTouchStates.begin() + index);
+            }
+            break;
+        }
+    }
+}
+
+/**
+ * Replace the coordinates in msg with the coordinates in lastResample, if necessary.
+ *
+ * If lastResample is no longer valid for a specific pointer (i.e. the lastResample time
+ * is in the past relative to msg and the past two events do not contain identical coordinates),
+ * then invalidate the lastResample data for that pointer.
+ * If the two past events have identical coordinates, then lastResample data for that pointer will
+ * remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is
+ * resampled to the new value x1, then x1 will always be used to replace x0 until some new value
+ * not equal to x0 is received.
+ */
+void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
+    nsecs_t eventTime = msg.body.motion.eventTime;
+    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+        uint32_t id = msg.body.motion.pointers[i].properties.id;
+        if (state.lastResample.idBits.hasBit(id)) {
+            if (eventTime < state.lastResample.eventTime ||
+                state.recentCoordinatesAreIdentical(id)) {
+                PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
+                const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
+                ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+                         resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
+                         msgCoords.getY());
+                msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
+                msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
+                msgCoords.isResampled = true;
+            } else {
+                state.lastResample.idBits.clearBit(id);
+            }
+        }
+    }
+}
+
+void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
+                                       const InputMessage* next) {
+    if (!mResampleTouch || !(isPointerEvent(event->getSource())) ||
+        event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
+        return;
+    }
+
+    ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
+    if (index < 0) {
+        ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
+        return;
+    }
+
+    TouchState& touchState = mTouchStates[index];
+    if (touchState.historySize < 1) {
+        ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
+        return;
+    }
+
+    // Ensure that the current sample has all of the pointers that need to be reported.
+    const History* current = touchState.getHistory(0);
+    size_t pointerCount = event->getPointerCount();
+    for (size_t i = 0; i < pointerCount; i++) {
+        uint32_t id = event->getPointerId(i);
+        if (!current->idBits.hasBit(id)) {
+            ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
+            return;
+        }
+    }
+
+    // Find the data to use for resampling.
+    const History* other;
+    History future;
+    float alpha;
+    if (next) {
+        // Interpolate between current sample and future sample.
+        // So current->eventTime <= sampleTime <= future.eventTime.
+        future.initializeFrom(*next);
+        other = &future;
+        nsecs_t delta = future.eventTime - current->eventTime;
+        if (delta < RESAMPLE_MIN_DELTA) {
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
+                     delta);
+            return;
+        }
+        alpha = float(sampleTime - current->eventTime) / delta;
+    } else if (touchState.historySize >= 2) {
+        // Extrapolate future sample using current sample and past sample.
+        // So other->eventTime <= current->eventTime <= sampleTime.
+        other = touchState.getHistory(1);
+        nsecs_t delta = current->eventTime - other->eventTime;
+        if (delta < RESAMPLE_MIN_DELTA) {
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
+                     delta);
+            return;
+        } else if (delta > RESAMPLE_MAX_DELTA) {
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
+                     delta);
+            return;
+        }
+        nsecs_t maxPredict = current->eventTime + std::min(delta / 2, RESAMPLE_MAX_PREDICTION);
+        if (sampleTime > maxPredict) {
+            ALOGD_IF(debugResampling(),
+                     "Sample time is too far in the future, adjusting prediction "
+                     "from %" PRId64 " to %" PRId64 " ns.",
+                     sampleTime - current->eventTime, maxPredict - current->eventTime);
+            sampleTime = maxPredict;
+        }
+        alpha = float(current->eventTime - sampleTime) / delta;
+    } else {
+        ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
+        return;
+    }
+
+    if (current->eventTime == sampleTime) {
+        // Prevents having 2 events with identical times and coordinates.
+        return;
+    }
+
+    // Resample touch coordinates.
+    History oldLastResample;
+    oldLastResample.initializeFrom(touchState.lastResample);
+    touchState.lastResample.eventTime = sampleTime;
+    touchState.lastResample.idBits.clear();
+    for (size_t i = 0; i < pointerCount; i++) {
+        uint32_t id = event->getPointerId(i);
+        touchState.lastResample.idToIndex[id] = i;
+        touchState.lastResample.idBits.markBit(id);
+        if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) {
+            // We maintain the previously resampled value for this pointer (stored in
+            // oldLastResample) when the coordinates for this pointer haven't changed since then.
+            // This way we don't introduce artificial jitter when pointers haven't actually moved.
+            // The isResampled flag isn't cleared as the values don't reflect what the device is
+            // actually reporting.
+
+            // We know here that the coordinates for the pointer haven't changed because we
+            // would've cleared the resampled bit in rewriteMessage if they had. We can't modify
+            // lastResample in place because the mapping from pointer ID to index may have changed.
+            touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
+            continue;
+        }
+
+        PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
+        const PointerCoords& currentCoords = current->getPointerById(id);
+        resampledCoords = currentCoords;
+        resampledCoords.isResampled = true;
+        if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
+            const PointerCoords& otherCoords = other->getPointerById(id);
+            resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
+                                         lerp(currentCoords.getX(), otherCoords.getX(), alpha));
+            resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                         lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+            ALOGD_IF(debugResampling(),
+                     "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+                     "other (%0.3f, %0.3f), alpha %0.3f",
+                     id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+                     currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
+        } else {
+            ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
+                     resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+                     currentCoords.getY());
+        }
+    }
+
+    event->addSample(sampleTime, touchState.lastResample.pointers);
+}
+
+status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
+             mChannel->getName().c_str(), seq, toString(handled));
+
+    if (!seq) {
+        ALOGE("Attempted to send a finished signal with sequence number 0.");
+        return BAD_VALUE;
+    }
+
+    // Send finished signals for the batch sequence chain first.
+    size_t seqChainCount = mSeqChains.size();
+    if (seqChainCount) {
+        uint32_t currentSeq = seq;
+        uint32_t chainSeqs[seqChainCount];
+        size_t chainIndex = 0;
+        for (size_t i = seqChainCount; i > 0;) {
+            i--;
+            const SeqChain& seqChain = mSeqChains[i];
+            if (seqChain.seq == currentSeq) {
+                currentSeq = seqChain.chain;
+                chainSeqs[chainIndex++] = currentSeq;
+                mSeqChains.erase(mSeqChains.begin() + i);
+            }
+        }
+        status_t status = OK;
+        while (!status && chainIndex > 0) {
+            chainIndex--;
+            status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
+        }
+        if (status) {
+            // An error occurred so at least one signal was not sent, reconstruct the chain.
+            for (;;) {
+                SeqChain seqChain;
+                seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
+                seqChain.chain = chainSeqs[chainIndex];
+                mSeqChains.push_back(seqChain);
+                if (!chainIndex) break;
+                chainIndex--;
+            }
+            return status;
+        }
+    }
+
+    // Send finished signal for the last message in the batch.
+    return sendUnchainedFinishedSignal(seq, handled);
+}
+
+status_t InputConsumer::sendTimeline(int32_t inputEventId,
+                                     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
+             ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
+             mChannel->getName().c_str(), inputEventId,
+             graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+             graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::TIMELINE;
+    msg.header.seq = 0;
+    msg.body.timeline.eventId = inputEventId;
+    msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline);
+    return mChannel->sendMessage(&msg);
+}
+
+nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const {
+    auto it = mConsumeTimes.find(seq);
+    // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
+    // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
+    LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
+                        seq);
+    return it->second;
+}
+
+void InputConsumer::popConsumeTime(uint32_t seq) {
+    mConsumeTimes.erase(seq);
+}
+
+status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::FINISHED;
+    msg.header.seq = seq;
+    msg.body.finished.handled = handled;
+    msg.body.finished.consumeTime = getConsumeTime(seq);
+    status_t result = mChannel->sendMessage(&msg);
+    if (result == OK) {
+        // Remove the consume time if the socket write succeeded. We will not need to ack this
+        // message anymore. If the socket write did not succeed, we will try again and will still
+        // need consume time.
+        popConsumeTime(seq);
+
+        // Trace the event processing timeline - event was just finished
+        ATRACE_ASYNC_END(mProcessingTraceTag.c_str(), /*cookie=*/seq);
+    }
+    return result;
+}
+
+bool InputConsumer::hasPendingBatch() const {
+    return !mBatches.empty();
+}
+
+int32_t InputConsumer::getPendingBatchSource() const {
+    if (mBatches.empty()) {
+        return AINPUT_SOURCE_CLASS_NONE;
+    }
+
+    const Batch& batch = mBatches[0];
+    const InputMessage& head = batch.samples[0];
+    return head.body.motion.source;
+}
+
+bool InputConsumer::probablyHasInput() const {
+    return hasPendingBatch() || mChannel->probablyHasInput();
+}
+
+ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
+    for (size_t i = 0; i < mBatches.size(); i++) {
+        const Batch& batch = mBatches[i];
+        const InputMessage& head = batch.samples[0];
+        if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
+    for (size_t i = 0; i < mTouchStates.size(); i++) {
+        const TouchState& touchState = mTouchStates[i];
+        if (touchState.deviceId == deviceId && touchState.source == source) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+bool InputConsumer::canAddSample(const Batch& batch, const InputMessage* msg) {
+    const InputMessage& head = batch.samples[0];
+    uint32_t pointerCount = msg->body.motion.pointerCount;
+    if (head.body.motion.pointerCount != pointerCount ||
+        head.body.motion.action != msg->body.motion.action) {
+        return false;
+    }
+    for (size_t i = 0; i < pointerCount; i++) {
+        if (head.body.motion.pointers[i].properties != msg->body.motion.pointers[i].properties) {
+            return false;
+        }
+    }
+    return true;
+}
+
+ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
+    size_t numSamples = batch.samples.size();
+    size_t index = 0;
+    while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) {
+        index += 1;
+    }
+    return ssize_t(index) - 1;
+}
+
+std::string InputConsumer::dump() const {
+    std::string out;
+    out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n";
+    out = out + "mChannel = " + mChannel->getName() + "\n";
+    out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
+    if (mMsgDeferred) {
+        out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n";
+    }
+    out += "Batches:\n";
+    for (const Batch& batch : mBatches) {
+        out += "    Batch:\n";
+        for (const InputMessage& msg : batch.samples) {
+            out += android::base::StringPrintf("        Message %" PRIu32 ": %s ", msg.header.seq,
+                                               ftl::enum_string(msg.header.type).c_str());
+            switch (msg.header.type) {
+                case InputMessage::Type::KEY: {
+                    out += android::base::StringPrintf("action=%s keycode=%" PRId32,
+                                                       KeyEvent::actionToString(
+                                                               msg.body.key.action),
+                                                       msg.body.key.keyCode);
+                    break;
+                }
+                case InputMessage::Type::MOTION: {
+                    out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action);
+                    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+                        const float x = msg.body.motion.pointers[i].coords.getX();
+                        const float y = msg.body.motion.pointers[i].coords.getY();
+                        out += android::base::StringPrintf("\n            Pointer %" PRIu32
+                                                           " : x=%.1f y=%.1f",
+                                                           i, x, y);
+                    }
+                    break;
+                }
+                case InputMessage::Type::FINISHED: {
+                    out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64,
+                                                       toString(msg.body.finished.handled),
+                                                       msg.body.finished.consumeTime);
+                    break;
+                }
+                case InputMessage::Type::FOCUS: {
+                    out += android::base::StringPrintf("hasFocus=%s",
+                                                       toString(msg.body.focus.hasFocus));
+                    break;
+                }
+                case InputMessage::Type::CAPTURE: {
+                    out += android::base::StringPrintf("hasCapture=%s",
+                                                       toString(msg.body.capture
+                                                                        .pointerCaptureEnabled));
+                    break;
+                }
+                case InputMessage::Type::DRAG: {
+                    out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s",
+                                                       msg.body.drag.x, msg.body.drag.y,
+                                                       toString(msg.body.drag.isExiting));
+                    break;
+                }
+                case InputMessage::Type::TIMELINE: {
+                    const nsecs_t gpuCompletedTime =
+                            msg.body.timeline
+                                    .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+                    const nsecs_t presentTime =
+                            msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+                    out += android::base::StringPrintf("inputEventId=%" PRId32
+                                                       ", gpuCompletedTime=%" PRId64
+                                                       ", presentTime=%" PRId64,
+                                                       msg.body.timeline.eventId, gpuCompletedTime,
+                                                       presentTime);
+                    break;
+                }
+                case InputMessage::Type::TOUCH_MODE: {
+                    out += android::base::StringPrintf("isInTouchMode=%s",
+                                                       toString(msg.body.touchMode.isInTouchMode));
+                    break;
+                }
+            }
+            out += "\n";
+        }
+    }
+    if (mBatches.empty()) {
+        out += "    <empty>\n";
+    }
+    out += "mSeqChains:\n";
+    for (const SeqChain& chain : mSeqChains) {
+        out += android::base::StringPrintf("    chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq,
+                                           chain.chain);
+    }
+    if (mSeqChains.empty()) {
+        out += "    <empty>\n";
+    }
+    out += "mConsumeTimes:\n";
+    for (const auto& [seq, consumeTime] : mConsumeTimes) {
+        out += android::base::StringPrintf("    seq = %" PRIu32 " consumeTime = %" PRId64, seq,
+                                           consumeTime);
+    }
+    if (mConsumeTimes.empty()) {
+        out += "    <empty>\n";
+    }
+    return out;
+}
+
+} // namespace android
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
new file mode 100644
index 0000000..15d992f
--- /dev/null
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -0,0 +1,539 @@
+/**
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputTransport"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include <inttypes.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
+#include <ftl/enum.h>
+#include <utils/Trace.h>
+
+#include <com_android_input_flags.h>
+#include <input/InputConsumerNoResampling.h>
+#include <input/PrintTools.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
+
+namespace android {
+
+namespace {
+
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+const bool DEBUG_TRANSPORT_CONSUMER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+std::unique_ptr<KeyEvent> createKeyEvent(const InputMessage& msg) {
+    std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
+    event->initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
+                      ui::LogicalDisplayId{msg.body.key.displayId}, msg.body.key.hmac,
+                      msg.body.key.action, msg.body.key.flags, msg.body.key.keyCode,
+                      msg.body.key.scanCode, msg.body.key.metaState, msg.body.key.repeatCount,
+                      msg.body.key.downTime, msg.body.key.eventTime);
+    return event;
+}
+
+std::unique_ptr<FocusEvent> createFocusEvent(const InputMessage& msg) {
+    std::unique_ptr<FocusEvent> event = std::make_unique<FocusEvent>();
+    event->initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+    return event;
+}
+
+std::unique_ptr<CaptureEvent> createCaptureEvent(const InputMessage& msg) {
+    std::unique_ptr<CaptureEvent> event = std::make_unique<CaptureEvent>();
+    event->initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+    return event;
+}
+
+std::unique_ptr<DragEvent> createDragEvent(const InputMessage& msg) {
+    std::unique_ptr<DragEvent> event = std::make_unique<DragEvent>();
+    event->initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
+                      msg.body.drag.isExiting);
+    return event;
+}
+
+std::unique_ptr<MotionEvent> createMotionEvent(const InputMessage& msg) {
+    std::unique_ptr<MotionEvent> event = std::make_unique<MotionEvent>();
+    const uint32_t pointerCount = msg.body.motion.pointerCount;
+    std::vector<PointerProperties> pointerProperties;
+    pointerProperties.reserve(pointerCount);
+    std::vector<PointerCoords> pointerCoords;
+    pointerCoords.reserve(pointerCount);
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back(msg.body.motion.pointers[i].properties);
+        pointerCoords.push_back(msg.body.motion.pointers[i].coords);
+    }
+
+    ui::Transform transform;
+    transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
+                   msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
+    ui::Transform displayTransform;
+    displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
+                          msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
+                          0, 0, 1});
+    event->initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
+                      ui::LogicalDisplayId{msg.body.motion.displayId}, msg.body.motion.hmac,
+                      msg.body.motion.action, msg.body.motion.actionButton, msg.body.motion.flags,
+                      msg.body.motion.edgeFlags, msg.body.motion.metaState,
+                      msg.body.motion.buttonState, msg.body.motion.classification, transform,
+                      msg.body.motion.xPrecision, msg.body.motion.yPrecision,
+                      msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition,
+                      displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime,
+                      pointerCount, pointerProperties.data(), pointerCoords.data());
+    return event;
+}
+
+void addSample(MotionEvent& event, const InputMessage& msg) {
+    uint32_t pointerCount = msg.body.motion.pointerCount;
+    std::vector<PointerCoords> pointerCoords;
+    pointerCoords.reserve(pointerCount);
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerCoords.push_back(msg.body.motion.pointers[i].coords);
+    }
+
+    // TODO(b/329770983): figure out if it's safe to combine events with mismatching metaState
+    event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
+    event.addSample(msg.body.motion.eventTime, pointerCoords.data());
+}
+
+std::unique_ptr<TouchModeEvent> createTouchModeEvent(const InputMessage& msg) {
+    std::unique_ptr<TouchModeEvent> event = std::make_unique<TouchModeEvent>();
+    event->initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+    return event;
+}
+
+std::string outboundMessageToString(const InputMessage& outboundMsg) {
+    switch (outboundMsg.header.type) {
+        case InputMessage::Type::FINISHED: {
+            return android::base::StringPrintf("  Finish: seq=%" PRIu32 " handled=%s",
+                                               outboundMsg.header.seq,
+                                               toString(outboundMsg.body.finished.handled));
+        }
+        case InputMessage::Type::TIMELINE: {
+            return android::base::
+                    StringPrintf("  Timeline: inputEventId=%" PRId32 " gpuCompletedTime=%" PRId64
+                                 ", presentTime=%" PRId64,
+                                 outboundMsg.body.timeline.eventId,
+                                 outboundMsg.body.timeline
+                                         .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+                                 outboundMsg.body.timeline
+                                         .graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+        }
+        default: {
+            LOG(FATAL) << "Outbound message must be FINISHED or TIMELINE, got "
+                       << ftl::enum_string(outboundMsg.header.type);
+            return "Unreachable";
+        }
+    }
+}
+
+InputMessage createFinishedMessage(uint32_t seq, bool handled, nsecs_t consumeTime) {
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::FINISHED;
+    msg.header.seq = seq;
+    msg.body.finished.handled = handled;
+    msg.body.finished.consumeTime = consumeTime;
+    return msg;
+}
+
+InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTime,
+                                   nsecs_t presentTime) {
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::TIMELINE;
+    msg.header.seq = 0;
+    msg.body.timeline.eventId = inputEventId;
+    msg.body.timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime;
+    msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
+    return msg;
+}
+
+} // namespace
+
+using android::base::Result;
+using android::base::StringPrintf;
+
+// --- InputConsumerNoResampling ---
+
+InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
+                                                     sp<Looper> looper,
+                                                     InputConsumerCallbacks& callbacks)
+      : mChannel(channel), mLooper(looper), mCallbacks(callbacks), mFdEvents(0) {
+    LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
+    mCallback = sp<LooperEventCallback>::make(
+            std::bind(&InputConsumerNoResampling::handleReceiveCallback, this,
+                      std::placeholders::_1));
+    // In the beginning, there are no pending outbounds events; we only care about receiving
+    // incoming data.
+    setFdEvents(ALOOPER_EVENT_INPUT);
+}
+
+InputConsumerNoResampling::~InputConsumerNoResampling() {
+    ensureCalledOnLooperThread(__func__);
+    consumeBatchedInputEvents(std::nullopt);
+    while (!mOutboundQueue.empty()) {
+        processOutboundEvents();
+        // This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
+        // so keep trying to send the events as long as they are present in the queue.
+    }
+    setFdEvents(0);
+}
+
+int InputConsumerNoResampling::handleReceiveCallback(int events) {
+    // Allowed return values of this function as documented in LooperCallback::handleEvent
+    constexpr int REMOVE_CALLBACK = 0;
+    constexpr int KEEP_CALLBACK = 1;
+
+    if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+        // This error typically occurs when the publisher has closed the input channel
+        // as part of removing a window or finishing an IME session, in which case
+        // the consumer will soon be disposed as well.
+        if (DEBUG_TRANSPORT_CONSUMER) {
+            LOG(INFO) << "The channel was hung up or an error occurred: " << mChannel->getName();
+        }
+        return REMOVE_CALLBACK;
+    }
+
+    int handledEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) {
+        std::vector<InputMessage> messages = readAllMessages();
+        handleMessages(std::move(messages));
+        handledEvents |= ALOOPER_EVENT_INPUT;
+    }
+
+    if (events & ALOOPER_EVENT_OUTPUT) {
+        processOutboundEvents();
+        handledEvents |= ALOOPER_EVENT_OUTPUT;
+    }
+    if (handledEvents != events) {
+        LOG(FATAL) << "Mismatch: handledEvents=" << handledEvents << ", events=" << events;
+    }
+    return KEEP_CALLBACK;
+}
+
+void InputConsumerNoResampling::processOutboundEvents() {
+    while (!mOutboundQueue.empty()) {
+        const InputMessage& outboundMsg = mOutboundQueue.front();
+
+        const status_t result = mChannel->sendMessage(&outboundMsg);
+        if (result == OK) {
+            if (outboundMsg.header.type == InputMessage::Type::FINISHED) {
+                ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/outboundMsg.header.seq);
+            }
+            // Successful send. Erase the entry and keep trying to send more
+            mOutboundQueue.pop();
+            continue;
+        }
+
+        // Publisher is busy, try again later. Keep this entry (do not erase)
+        if (result == WOULD_BLOCK) {
+            setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
+            return; // try again later
+        }
+
+        // Some other error. Give up
+        LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName()
+                   << "'.  status=" << statusToString(result) << "(" << result << ")";
+    }
+
+    // The queue is now empty. Tell looper there's no more output to expect.
+    setFdEvents(ALOOPER_EVENT_INPUT);
+}
+
+void InputConsumerNoResampling::finishInputEvent(uint32_t seq, bool handled) {
+    ensureCalledOnLooperThread(__func__);
+    mOutboundQueue.push(createFinishedMessage(seq, handled, popConsumeTime(seq)));
+    // also produce finish events for all batches for this seq (if any)
+    const auto it = mBatchedSequenceNumbers.find(seq);
+    if (it != mBatchedSequenceNumbers.end()) {
+        for (uint32_t subSeq : it->second) {
+            mOutboundQueue.push(createFinishedMessage(subSeq, handled, popConsumeTime(subSeq)));
+        }
+        mBatchedSequenceNumbers.erase(it);
+    }
+    processOutboundEvents();
+}
+
+bool InputConsumerNoResampling::probablyHasInput() const {
+    // Ideally, this would only be allowed to run on the looper thread, and in production, it will.
+    // However, for testing, it's convenient to call this while the looper thread is blocked, so
+    // we do not call ensureCalledOnLooperThread here.
+    return (!mBatches.empty()) || mChannel->probablyHasInput();
+}
+
+void InputConsumerNoResampling::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
+                                               nsecs_t presentTime) {
+    ensureCalledOnLooperThread(__func__);
+    mOutboundQueue.push(createTimelineMessage(inputEventId, gpuCompletedTime, presentTime));
+    processOutboundEvents();
+}
+
+nsecs_t InputConsumerNoResampling::popConsumeTime(uint32_t seq) {
+    auto it = mConsumeTimes.find(seq);
+    // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
+    // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
+    LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
+                        seq);
+    nsecs_t consumeTime = it->second;
+    mConsumeTimes.erase(it);
+    return consumeTime;
+}
+
+void InputConsumerNoResampling::setFdEvents(int events) {
+    if (mFdEvents != events) {
+        mFdEvents = events;
+        if (events != 0) {
+            mLooper->addFd(mChannel->getFd(), 0, events, mCallback, nullptr);
+        } else {
+            mLooper->removeFd(mChannel->getFd());
+        }
+    }
+}
+
+void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) {
+    // TODO(b/297226446) : add resampling
+    for (const InputMessage& msg : messages) {
+        if (msg.header.type == InputMessage::Type::MOTION) {
+            const int32_t action = msg.body.motion.action;
+            const DeviceId deviceId = msg.body.motion.deviceId;
+            const int32_t source = msg.body.motion.source;
+            const bool batchableEvent = (action == AMOTION_EVENT_ACTION_MOVE ||
+                                         action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
+                    (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
+                     isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+            if (batchableEvent) {
+                // add it to batch
+                mBatches[deviceId].emplace(msg);
+            } else {
+                // consume all pending batches for this event immediately
+                // TODO(b/329776327): figure out if this could be smarter by limiting the
+                // consumption only to the current device.
+                consumeBatchedInputEvents(std::nullopt);
+                handleMessage(msg);
+            }
+        } else {
+            // Non-motion events shouldn't force the consumption of pending batched events
+            handleMessage(msg);
+        }
+    }
+    // At the end of this, if we still have pending batches, notify the receiver about it.
+
+    // We need to carefully notify the InputConsumerCallbacks about the pending batch. The receiver
+    // could choose to consume all events when notified about the batch. That means that the
+    // "mBatches" variable could change when 'InputConsumerCallbacks::onBatchedInputEventPending' is
+    // invoked. We also can't notify the InputConsumerCallbacks in a while loop until mBatches is
+    // empty, because the receiver could choose to not consume the batch immediately.
+    std::set<int32_t> pendingBatchSources;
+    for (const auto& [_, pendingMessages] : mBatches) {
+        // Assume that all messages for a given device has the same source.
+        pendingBatchSources.insert(pendingMessages.front().body.motion.source);
+    }
+    for (const int32_t source : pendingBatchSources) {
+        const bool sourceStillRemaining =
+                std::any_of(mBatches.begin(), mBatches.end(), [=](const auto& pair) {
+                    return pair.second.front().body.motion.source == source;
+                });
+        if (sourceStillRemaining) {
+            mCallbacks.onBatchedInputEventPending(source);
+        }
+    }
+}
+
+std::vector<InputMessage> InputConsumerNoResampling::readAllMessages() {
+    std::vector<InputMessage> messages;
+    while (true) {
+        InputMessage msg;
+        status_t result = mChannel->receiveMessage(&msg);
+        switch (result) {
+            case OK: {
+                const auto [_, inserted] =
+                        mConsumeTimes.emplace(msg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+                LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+                                    msg.header.seq);
+
+                // Trace the event processing timeline - event was just read from the socket
+                // TODO(b/329777420): distinguish between multiple instances of InputConsumer
+                // in the same process.
+                ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/msg.header.seq);
+                messages.push_back(msg);
+                break;
+            }
+            case WOULD_BLOCK: {
+                return messages;
+            }
+            case DEAD_OBJECT: {
+                LOG(FATAL) << "Got a dead object for " << mChannel->getName();
+                break;
+            }
+            case BAD_VALUE: {
+                LOG(FATAL) << "Got a bad value for " << mChannel->getName();
+                break;
+            }
+            default: {
+                LOG(FATAL) << "Unexpected error: " << result;
+                break;
+            }
+        }
+    }
+}
+
+void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const {
+    switch (msg.header.type) {
+        case InputMessage::Type::KEY: {
+            std::unique_ptr<KeyEvent> keyEvent = createKeyEvent(msg);
+            mCallbacks.onKeyEvent(std::move(keyEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::MOTION: {
+            std::unique_ptr<MotionEvent> motionEvent = createMotionEvent(msg);
+            mCallbacks.onMotionEvent(std::move(motionEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::FINISHED:
+        case InputMessage::Type::TIMELINE: {
+            LOG(FATAL) << "Consumed a " << ftl::enum_string(msg.header.type)
+                       << " message, which should never be seen by InputConsumer on "
+                       << mChannel->getName();
+            break;
+        }
+
+        case InputMessage::Type::FOCUS: {
+            std::unique_ptr<FocusEvent> focusEvent = createFocusEvent(msg);
+            mCallbacks.onFocusEvent(std::move(focusEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::CAPTURE: {
+            std::unique_ptr<CaptureEvent> captureEvent = createCaptureEvent(msg);
+            mCallbacks.onCaptureEvent(std::move(captureEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::DRAG: {
+            std::unique_ptr<DragEvent> dragEvent = createDragEvent(msg);
+            mCallbacks.onDragEvent(std::move(dragEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::TOUCH_MODE: {
+            std::unique_ptr<TouchModeEvent> touchModeEvent = createTouchModeEvent(msg);
+            mCallbacks.onTouchModeEvent(std::move(touchModeEvent), msg.header.seq);
+            break;
+        }
+    }
+}
+
+bool InputConsumerNoResampling::consumeBatchedInputEvents(
+        std::optional<nsecs_t> requestedFrameTime) {
+    ensureCalledOnLooperThread(__func__);
+    // When batching is not enabled, we want to consume all events. That's equivalent to having an
+    // infinite frameTime.
+    const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
+    bool producedEvents = false;
+    for (auto& [deviceId, messages] : mBatches) {
+        std::unique_ptr<MotionEvent> motion;
+        std::optional<uint32_t> firstSeqForBatch;
+        std::vector<uint32_t> sequences;
+        while (!messages.empty()) {
+            const InputMessage& msg = messages.front();
+            if (msg.body.motion.eventTime > frameTime) {
+                break;
+            }
+            if (motion == nullptr) {
+                motion = createMotionEvent(msg);
+                firstSeqForBatch = msg.header.seq;
+                const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}});
+                if (!inserted) {
+                    LOG(FATAL) << "The sequence " << msg.header.seq << " was already present!";
+                }
+            } else {
+                addSample(*motion, msg);
+                mBatchedSequenceNumbers[*firstSeqForBatch].push_back(msg.header.seq);
+            }
+            messages.pop();
+        }
+        if (motion != nullptr) {
+            LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value());
+            mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
+            producedEvents = true;
+        } else {
+            // This is OK, it just means that the frameTime is too old (all events that we have
+            // pending are in the future of the frametime). Maybe print a
+            // warning? If there are multiple devices active though, this might be normal and can
+            // just be ignored, unless none of them resulted in any consumption (in that case, this
+            // function would already return "false" so we could just leave it up to the caller).
+        }
+    }
+    std::erase_if(mBatches, [](const auto& pair) { return pair.second.empty(); });
+    return producedEvents;
+}
+
+void InputConsumerNoResampling::ensureCalledOnLooperThread(const char* func) const {
+    sp<Looper> callingThreadLooper = Looper::getForThread();
+    if (callingThreadLooper != mLooper) {
+        LOG(FATAL) << "The function " << func << " can only be called on the looper thread";
+    }
+}
+
+std::string InputConsumerNoResampling::dump() const {
+    ensureCalledOnLooperThread(__func__);
+    std::string out;
+    if (mOutboundQueue.empty()) {
+        out += "mOutboundQueue: <empty>\n";
+    } else {
+        out += "mOutboundQueue:\n";
+        // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
+        // doesn't provide a good way to iterate over the entire container.
+        std::queue<InputMessage> tmpQueue = mOutboundQueue;
+        while (!tmpQueue.empty()) {
+            out += std::string("  ") + outboundMessageToString(tmpQueue.front()) + "\n";
+            tmpQueue.pop();
+        }
+    }
+
+    if (mBatches.empty()) {
+        out += "mBatches: <empty>\n";
+    } else {
+        out += "mBatches:\n";
+        for (const auto& [deviceId, messages] : mBatches) {
+            out += "  Device id ";
+            out += std::to_string(deviceId);
+            out += ":\n";
+            // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
+            // doesn't provide a good way to iterate over the entire container.
+            std::queue<InputMessage> tmpQueue = messages;
+            while (!tmpQueue.empty()) {
+                LOG_ALWAYS_FATAL_IF(tmpQueue.front().header.type != InputMessage::Type::MOTION);
+                std::unique_ptr<MotionEvent> motion = createMotionEvent(tmpQueue.front());
+                out += std::string("    ") + streamableToString(*motion) + "\n";
+                tmpQueue.pop();
+            }
+        }
+    }
+
+    return out;
+}
+
+} // namespace android
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index ccc8323..bc67810 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -23,7 +23,6 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <ftl/enum.h>
-#include <gui/constants.h>
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
 
@@ -170,7 +169,7 @@
 // --- InputDeviceInfo ---
 
 InputDeviceInfo::InputDeviceInfo() {
-    initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ADISPLAY_ID_NONE);
+    initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ui::LogicalDisplayId::INVALID);
 }
 
 InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
@@ -187,20 +186,24 @@
         mKeyCharacterMap(other.mKeyCharacterMap),
         mUsiVersion(other.mUsiVersion),
         mAssociatedDisplayId(other.mAssociatedDisplayId),
+        mEnabled(other.mEnabled),
         mHasVibrator(other.mHasVibrator),
         mHasBattery(other.mHasBattery),
         mHasButtonUnderPad(other.mHasButtonUnderPad),
         mHasSensor(other.mHasSensor),
         mMotionRanges(other.mMotionRanges),
         mSensors(other.mSensors),
-        mLights(other.mLights) {}
+        mLights(other.mLights),
+        mViewBehavior(other.mViewBehavior) {}
 
 InputDeviceInfo::~InputDeviceInfo() {
 }
 
 void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                                  const InputDeviceIdentifier& identifier, const std::string& alias,
-                                 bool isExternal, bool hasMic, int32_t associatedDisplayId) {
+                                 bool isExternal, bool hasMic,
+                                 ui::LogicalDisplayId associatedDisplayId,
+                                 InputDeviceViewBehavior viewBehavior, bool enabled) {
     mId = id;
     mGeneration = generation;
     mControllerNumber = controllerNumber;
@@ -211,10 +214,12 @@
     mSources = 0;
     mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
     mAssociatedDisplayId = associatedDisplayId;
+    mEnabled = enabled;
     mHasVibrator = false;
     mHasBattery = false;
     mHasButtonUnderPad = false;
     mHasSensor = false;
+    mViewBehavior = viewBehavior;
     mUsiVersion.reset();
     mMotionRanges.clear();
     mSensors.clear();
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 0e627e5..8db0ca5 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -348,7 +348,9 @@
     DEFINE_KEYCODE(MACRO_1), \
     DEFINE_KEYCODE(MACRO_2), \
     DEFINE_KEYCODE(MACRO_3), \
-    DEFINE_KEYCODE(MACRO_4)
+    DEFINE_KEYCODE(MACRO_4), \
+    DEFINE_KEYCODE(EMOJI_PICKER), \
+    DEFINE_KEYCODE(SCREENSHOT)
 
 // NOTE: If you add a new axis here you must also add it to several other files.
 //       Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 09e98d0..47b4228 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -10,6 +10,7 @@
 #include <fcntl.h>
 #include <inttypes.h>
 #include <math.h>
+#include <poll.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -25,10 +26,13 @@
 
 #include <com_android_input_flags.h>
 #include <input/InputTransport.h>
+#include <input/PrintTools.h>
 #include <input/TraceTools.h>
 
 namespace input_flags = com::android::input::flags;
 
+namespace android {
+
 namespace {
 
 /**
@@ -47,14 +51,6 @@
 const bool DEBUG_CHANNEL_LIFECYCLE =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Lifecycle", ANDROID_LOG_INFO);
 
-/**
- * Log debug messages relating to the consumer end of the transport channel.
- * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
- */
-
-const bool DEBUG_TRANSPORT_CONSUMER =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
-
 const bool IS_DEBUGGABLE_BUILD =
 #if defined(__ANDROID__)
         android::base::GetBoolProperty("ro.debuggable", false);
@@ -77,21 +73,34 @@
     return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO);
 }
 
-/**
- * Log debug messages about touch event resampling.
- *
- * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
- * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
- * on debuggable builds (e.g. userdebug).
- */
-bool debugResampling() {
-    if (!IS_DEBUGGABLE_BUILD) {
-        static const bool DEBUG_TRANSPORT_RESAMPLING =
-                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
-                                          ANDROID_LOG_INFO);
-        return DEBUG_TRANSPORT_RESAMPLING;
+android::base::unique_fd dupChannelFd(int fd) {
+    android::base::unique_fd newFd(::dup(fd));
+    if (!newFd.ok()) {
+        ALOGE("Could not duplicate fd %i : %s", fd, strerror(errno));
+        const bool hitFdLimit = errno == EMFILE || errno == ENFILE;
+        // If this process is out of file descriptors, then throwing that might end up exploding
+        // on the other side of a binder call, which isn't really helpful.
+        // Better to just crash here and hope that the FD leak is slow.
+        // Other failures could be client errors, so we still propagate those back to the caller.
+        LOG_ALWAYS_FATAL_IF(hitFdLimit, "Too many open files, could not duplicate input channel");
+        return {};
     }
-    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+    return newFd;
+}
+
+// Socket buffer size.  The default is typically about 128KB, which is much larger than
+// we really need.  So we make it smaller.  It just needs to be big enough to hold
+// a few dozen large multi-finger motion events in the case where an application gets
+// behind processing touches.
+constexpr size_t SOCKET_BUFFER_SIZE = 32 * 1024;
+
+/**
+ * Crash if the events that are getting sent to the InputPublisher are inconsistent.
+ * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
+ */
+bool verifyEvents() {
+    return input_flags::enable_outbound_event_verification() ||
+            __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
 }
 
 } // namespace
@@ -99,74 +108,6 @@
 using android::base::Result;
 using android::base::StringPrintf;
 
-namespace android {
-
-// Socket buffer size.  The default is typically about 128KB, which is much larger than
-// we really need.  So we make it smaller.  It just needs to be big enough to hold
-// a few dozen large multi-finger motion events in the case where an application gets
-// behind processing touches.
-static const size_t SOCKET_BUFFER_SIZE = 32 * 1024;
-
-// Nanoseconds per milliseconds.
-static const nsecs_t NANOS_PER_MS = 1000000;
-
-// Latency added during resampling.  A few milliseconds doesn't hurt much but
-// reduces the impact of mispredicted touch positions.
-const std::chrono::duration RESAMPLE_LATENCY = 5ms;
-
-// Minimum time difference between consecutive samples before attempting to resample.
-static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
-
-// Maximum time difference between consecutive samples before attempting to resample
-// by extrapolation.
-static const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS;
-
-// Maximum time to predict forward from the last known state, to avoid predicting too
-// far into the future.  This time is further bounded by 50% of the last time delta.
-static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
-
-/**
- * System property for enabling / disabling touch resampling.
- * Resampling extrapolates / interpolates the reported touch event coordinates to better
- * align them to the VSYNC signal, thus resulting in smoother scrolling performance.
- * Resampling is not needed (and should be disabled) on hardware that already
- * has touch events triggered by VSYNC.
- * Set to "1" to enable resampling (default).
- * Set to "0" to disable resampling.
- * Resampling is enabled by default.
- */
-static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
-
-/**
- * Crash if the events that are getting sent to the InputPublisher are inconsistent.
- * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
- */
-static bool verifyEvents() {
-    return input_flags::enable_outbound_event_verification() ||
-            __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
-}
-
-template<typename T>
-inline static T min(const T& a, const T& b) {
-    return a < b ? a : b;
-}
-
-inline static float lerp(float a, float b, float alpha) {
-    return a + alpha * (b - a);
-}
-
-inline static bool isPointerEvent(int32_t source) {
-    return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
-}
-
-inline static const char* toString(bool value) {
-    return value ? "true" : "false";
-}
-
-static bool shouldResampleTool(ToolType toolType) {
-    return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN;
-}
-
 // --- InputMessage ---
 
 bool InputMessage::isValid(size_t actualSize) const {
@@ -394,15 +335,23 @@
     return std::unique_ptr<InputChannel>(new InputChannel(name, std::move(fd), token));
 }
 
-InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token)
-      : mName(std::move(name)), mFd(std::move(fd)), mToken(std::move(token)) {
+std::unique_ptr<InputChannel> InputChannel::create(
+        android::os::InputChannelCore&& parceledChannel) {
+    return InputChannel::create(parceledChannel.name, parceledChannel.fd.release(),
+                                parceledChannel.token);
+}
+
+InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token) {
+    this->name = std::move(name);
+    this->fd.reset(std::move(fd));
+    this->token = std::move(token);
     ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel constructed: name='%s', fd=%d",
-             getName().c_str(), getFd().get());
+             getName().c_str(), getFd());
 }
 
 InputChannel::~InputChannel() {
     ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel destroyed: name='%s', fd=%d",
-             getName().c_str(), getFd().get());
+             getName().c_str(), getFd());
 }
 
 status_t InputChannel::openInputChannelPair(const std::string& name,
@@ -438,21 +387,21 @@
 
 status_t InputChannel::sendMessage(const InputMessage* msg) {
     ATRACE_NAME_IF(ATRACE_ENABLED(),
-                   StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32
-                                ")",
-                                mName.c_str(), msg->header.seq, msg->header.type));
+                   StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=%s)",
+                                name.c_str(), msg->header.seq,
+                                ftl::enum_string(msg->header.type).c_str()));
     const size_t msgLength = msg->size();
     InputMessage cleanMsg;
     msg->getSanitizedCopy(&cleanMsg);
     ssize_t nWrite;
     do {
-        nWrite = ::send(getFd().get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
+        nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
     } while (nWrite == -1 && errno == EINTR);
 
     if (nWrite < 0) {
         int error = errno;
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ error sending message of type %s, %s",
-                 mName.c_str(), ftl::enum_string(msg->header.type).c_str(), strerror(error));
+                 name.c_str(), ftl::enum_string(msg->header.type).c_str(), strerror(error));
         if (error == EAGAIN || error == EWOULDBLOCK) {
             return WOULD_BLOCK;
         }
@@ -464,12 +413,12 @@
 
     if (size_t(nWrite) != msgLength) {
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
-                 "channel '%s' ~ error sending message type %s, send was incomplete", mName.c_str(),
+                 "channel '%s' ~ error sending message type %s, send was incomplete", name.c_str(),
                  ftl::enum_string(msg->header.type).c_str());
         return DEAD_OBJECT;
     }
 
-    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(),
+    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", name.c_str(),
              ftl::enum_string(msg->header.type).c_str());
 
     return OK;
@@ -478,13 +427,13 @@
 status_t InputChannel::receiveMessage(InputMessage* msg) {
     ssize_t nRead;
     do {
-        nRead = ::recv(getFd().get(), msg, sizeof(InputMessage), MSG_DONTWAIT);
+        nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
     } while (nRead == -1 && errno == EINTR);
 
     if (nRead < 0) {
         int error = errno;
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ receive message failed, errno=%d",
-                 mName.c_str(), errno);
+                 name.c_str(), errno);
         if (error == EAGAIN || error == EWOULDBLOCK) {
             return WOULD_BLOCK;
         }
@@ -496,87 +445,92 @@
 
     if (nRead == 0) { // check for EOF
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
-                 "channel '%s' ~ receive message failed because peer was closed", mName.c_str());
+                 "channel '%s' ~ receive message failed because peer was closed", name.c_str());
         return DEAD_OBJECT;
     }
 
     if (!msg->isValid(nRead)) {
-        ALOGE("channel '%s' ~ received invalid message of size %zd", mName.c_str(), nRead);
+        ALOGE("channel '%s' ~ received invalid message of size %zd", name.c_str(), nRead);
         return BAD_VALUE;
     }
 
-    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(),
+    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", name.c_str(),
              ftl::enum_string(msg->header.type).c_str());
     if (ATRACE_ENABLED()) {
         // Add an additional trace point to include data about the received message.
-        std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32
-                                           ", type=0x%" PRIx32 ")",
-                                           mName.c_str(), msg->header.seq, msg->header.type);
+        std::string message =
+                StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=%s)",
+                             name.c_str(), msg->header.seq,
+                             ftl::enum_string(msg->header.type).c_str());
         ATRACE_NAME(message.c_str());
     }
     return OK;
 }
 
+bool InputChannel::probablyHasInput() const {
+    struct pollfd pfds = {.fd = fd.get(), .events = POLLIN};
+    if (::poll(&pfds, /*nfds=*/1, /*timeout=*/0) <= 0) {
+        // This can be a false negative because EINTR and ENOMEM are not handled. The latter should
+        // be extremely rare. The EINTR is also unlikely because it happens only when the signal
+        // arrives while the syscall is executed, and the syscall is quick. Hitting EINTR too often
+        // would be a sign of having too many signals, which is a bigger performance problem. A
+        // common tradition is to repeat the syscall on each EINTR, but it is not necessary here.
+        // In other words, the missing one liner is replaced by a multiline explanation.
+        return false;
+    }
+    // From poll(2): The bits returned in |revents| can include any of those specified in |events|,
+    // or one of the values POLLERR, POLLHUP, or POLLNVAL.
+    return (pfds.revents & POLLIN) != 0;
+}
+
+void InputChannel::waitForMessage(std::chrono::milliseconds timeout) const {
+    if (timeout < 0ms) {
+        LOG(FATAL) << "Timeout cannot be negative, received " << timeout.count();
+    }
+    struct pollfd pfds = {.fd = fd.get(), .events = POLLIN};
+    int ret;
+    std::chrono::time_point<std::chrono::steady_clock> stopTime =
+            std::chrono::steady_clock::now() + timeout;
+    std::chrono::milliseconds remaining = timeout;
+    do {
+        ret = ::poll(&pfds, /*nfds=*/1, /*timeout=*/remaining.count());
+        remaining = std::chrono::duration_cast<std::chrono::milliseconds>(
+                stopTime - std::chrono::steady_clock::now());
+    } while (ret == -1 && errno == EINTR && remaining > 0ms);
+}
+
 std::unique_ptr<InputChannel> InputChannel::dup() const {
-    base::unique_fd newFd(dupFd());
+    base::unique_fd newFd(dupChannelFd(fd.get()));
     return InputChannel::create(getName(), std::move(newFd), getConnectionToken());
 }
 
-void InputChannel::copyTo(InputChannel& outChannel) const {
-    outChannel.mName = getName();
-    outChannel.mFd = dupFd();
-    outChannel.mToken = getConnectionToken();
+void InputChannel::copyTo(android::os::InputChannelCore& outChannel) const {
+    outChannel.name = getName();
+    outChannel.fd.reset(dupChannelFd(fd.get()));
+    outChannel.token = getConnectionToken();
 }
 
-status_t InputChannel::writeToParcel(android::Parcel* parcel) const {
-    if (parcel == nullptr) {
-        ALOGE("%s: Null parcel", __func__);
-        return BAD_VALUE;
-    }
-    return parcel->writeStrongBinder(mToken)
-            ?: parcel->writeUtf8AsUtf16(mName) ?: parcel->writeUniqueFileDescriptor(mFd);
-}
-
-status_t InputChannel::readFromParcel(const android::Parcel* parcel) {
-    if (parcel == nullptr) {
-        ALOGE("%s: Null parcel", __func__);
-        return BAD_VALUE;
-    }
-    mToken = parcel->readStrongBinder();
-    return parcel->readUtf8FromUtf16(&mName) ?: parcel->readUniqueFileDescriptor(&mFd);
+void InputChannel::moveChannel(std::unique_ptr<InputChannel> from,
+                               android::os::InputChannelCore& outChannel) {
+    outChannel.name = from->getName();
+    outChannel.fd = android::os::ParcelFileDescriptor(std::move(from->fd));
+    outChannel.token = from->getConnectionToken();
 }
 
 sp<IBinder> InputChannel::getConnectionToken() const {
-    return mToken;
-}
-
-base::unique_fd InputChannel::dupFd() const {
-    base::unique_fd newFd(::dup(getFd().get()));
-    if (!newFd.ok()) {
-        ALOGE("Could not duplicate fd %i for channel %s: %s", getFd().get(), getName().c_str(),
-              strerror(errno));
-        const bool hitFdLimit = errno == EMFILE || errno == ENFILE;
-        // If this process is out of file descriptors, then throwing that might end up exploding
-        // on the other side of a binder call, which isn't really helpful.
-        // Better to just crash here and hope that the FD leak is slow.
-        // Other failures could be client errors, so we still propagate those back to the caller.
-        LOG_ALWAYS_FATAL_IF(hitFdLimit, "Too many open files, could not duplicate input channel %s",
-                            getName().c_str());
-        return {};
-    }
-    return newFd;
+    return token;
 }
 
 // --- InputPublisher ---
 
 InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel)
-      : mChannel(channel), mInputVerifier(channel->getName()) {}
+      : mChannel(channel), mInputVerifier(mChannel->getName()) {}
 
 InputPublisher::~InputPublisher() {
 }
 
 status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t deviceId,
-                                         int32_t source, int32_t displayId,
+                                         int32_t source, ui::LogicalDisplayId displayId,
                                          std::array<uint8_t, 32> hmac, int32_t action,
                                          int32_t flags, int32_t keyCode, int32_t scanCode,
                                          int32_t metaState, int32_t repeatCount, nsecs_t downTime,
@@ -604,7 +558,7 @@
     msg.body.key.eventId = eventId;
     msg.body.key.deviceId = deviceId;
     msg.body.key.source = source;
-    msg.body.key.displayId = displayId;
+    msg.body.key.displayId = displayId.val();
     msg.body.key.hmac = std::move(hmac);
     msg.body.key.action = action;
     msg.body.key.flags = flags;
@@ -618,11 +572,11 @@
 }
 
 status_t InputPublisher::publishMotionEvent(
-        uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,
-        std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
-        int32_t edgeFlags, int32_t metaState, int32_t buttonState,
-        MotionClassification classification, const ui::Transform& transform, float xPrecision,
-        float yPrecision, float xCursorPosition, float yCursorPosition,
+        uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source,
+        ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac, int32_t action,
+        int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
+        int32_t buttonState, MotionClassification classification, const ui::Transform& transform,
+        float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition,
         const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
         uint32_t pointerCount, const PointerProperties* pointerProperties,
         const PointerCoords* pointerCoords) {
@@ -642,13 +596,13 @@
         std::string transformString;
         transform.dump(transformString, "transform", "        ");
         ALOGD("channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, "
-              "displayId=%" PRId32 ", "
+              "displayId=%s, "
               "action=%s, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
               "metaState=0x%x, buttonState=0x%x, classification=%s,"
               "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", "
-              "pointerCount=%" PRIu32 " \n%s",
+              "pointerCount=%" PRIu32 "\n%s",
               mChannel->getName().c_str(), __func__, seq, eventId, deviceId,
-              inputEventSourceToString(source).c_str(), displayId,
+              inputEventSourceToString(source).c_str(), displayId.toString().c_str(),
               MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags,
               metaState, buttonState, motionClassificationToString(classification), xPrecision,
               yPrecision, downTime, eventTime, pointerCount, transformString.c_str());
@@ -671,7 +625,7 @@
     msg.body.motion.eventId = eventId;
     msg.body.motion.deviceId = deviceId;
     msg.body.motion.source = source;
-    msg.body.motion.displayId = displayId;
+    msg.body.motion.displayId = displayId.val();
     msg.body.motion.hmac = std::move(hmac);
     msg.body.motion.action = action;
     msg.body.motion.actionButton = actionButton;
@@ -810,809 +764,4 @@
     return android::base::Error(UNKNOWN_ERROR);
 }
 
-// --- InputConsumer ---
-
-InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
-      : InputConsumer(channel, isTouchResamplingEnabled()) {}
-
-InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
-                             bool enableTouchResampling)
-      : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {}
-
-InputConsumer::~InputConsumer() {
-}
-
-bool InputConsumer::isTouchResamplingEnabled() {
-    return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
-}
-
-status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
-                                nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
-    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-             "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
-             mChannel->getName().c_str(), toString(consumeBatches), frameTime);
-
-    *outSeq = 0;
-    *outEvent = nullptr;
-
-    // Fetch the next input message.
-    // Loop until an event can be returned or no additional events are received.
-    while (!*outEvent) {
-        if (mMsgDeferred) {
-            // mMsg contains a valid input message from the previous call to consume
-            // that has not yet been processed.
-            mMsgDeferred = false;
-        } else {
-            // Receive a fresh message.
-            status_t result = mChannel->receiveMessage(&mMsg);
-            if (result == OK) {
-                const auto [_, inserted] =
-                        mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
-                LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
-                                    mMsg.header.seq);
-            }
-            if (result) {
-                // Consume the next batched event unless batches are being held for later.
-                if (consumeBatches || result != WOULD_BLOCK) {
-                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
-                    if (*outEvent) {
-                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                                 "channel '%s' consumer ~ consumed batch event, seq=%u",
-                                 mChannel->getName().c_str(), *outSeq);
-                        break;
-                    }
-                }
-                return result;
-            }
-        }
-
-        switch (mMsg.header.type) {
-            case InputMessage::Type::KEY: {
-                KeyEvent* keyEvent = factory->createKeyEvent();
-                if (!keyEvent) return NO_MEMORY;
-
-                initializeKeyEvent(keyEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = keyEvent;
-                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                         "channel '%s' consumer ~ consumed key event, seq=%u",
-                         mChannel->getName().c_str(), *outSeq);
-                break;
-            }
-
-            case InputMessage::Type::MOTION: {
-                ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
-                if (batchIndex >= 0) {
-                    Batch& batch = mBatches[batchIndex];
-                    if (canAddSample(batch, &mMsg)) {
-                        batch.samples.push_back(mMsg);
-                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                                 "channel '%s' consumer ~ appended to batch event",
-                                 mChannel->getName().c_str());
-                        break;
-                    } else if (isPointerEvent(mMsg.body.motion.source) &&
-                               mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
-                        // No need to process events that we are going to cancel anyways
-                        const size_t count = batch.samples.size();
-                        for (size_t i = 0; i < count; i++) {
-                            const InputMessage& msg = batch.samples[i];
-                            sendFinishedSignal(msg.header.seq, false);
-                        }
-                        batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
-                        mBatches.erase(mBatches.begin() + batchIndex);
-                    } else {
-                        // We cannot append to the batch in progress, so we need to consume
-                        // the previous batch right now and defer the new message until later.
-                        mMsgDeferred = true;
-                        status_t result = consumeSamples(factory, batch, batch.samples.size(),
-                                                         outSeq, outEvent);
-                        mBatches.erase(mBatches.begin() + batchIndex);
-                        if (result) {
-                            return result;
-                        }
-                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                                 "channel '%s' consumer ~ consumed batch event and "
-                                 "deferred current event, seq=%u",
-                                 mChannel->getName().c_str(), *outSeq);
-                        break;
-                    }
-                }
-
-                // Start a new batch if needed.
-                if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
-                    mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
-                    Batch batch;
-                    batch.samples.push_back(mMsg);
-                    mBatches.push_back(batch);
-                    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                             "channel '%s' consumer ~ started batch event",
-                             mChannel->getName().c_str());
-                    break;
-                }
-
-                MotionEvent* motionEvent = factory->createMotionEvent();
-                if (!motionEvent) return NO_MEMORY;
-
-                updateTouchState(mMsg);
-                initializeMotionEvent(motionEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = motionEvent;
-
-                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                         "channel '%s' consumer ~ consumed motion event, seq=%u",
-                         mChannel->getName().c_str(), *outSeq);
-                break;
-            }
-
-            case InputMessage::Type::FINISHED:
-            case InputMessage::Type::TIMELINE: {
-                LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
-                                 "InputConsumer!",
-                                 ftl::enum_string(mMsg.header.type).c_str());
-                break;
-            }
-
-            case InputMessage::Type::FOCUS: {
-                FocusEvent* focusEvent = factory->createFocusEvent();
-                if (!focusEvent) return NO_MEMORY;
-
-                initializeFocusEvent(focusEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = focusEvent;
-                break;
-            }
-
-            case InputMessage::Type::CAPTURE: {
-                CaptureEvent* captureEvent = factory->createCaptureEvent();
-                if (!captureEvent) return NO_MEMORY;
-
-                initializeCaptureEvent(captureEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = captureEvent;
-                break;
-            }
-
-            case InputMessage::Type::DRAG: {
-                DragEvent* dragEvent = factory->createDragEvent();
-                if (!dragEvent) return NO_MEMORY;
-
-                initializeDragEvent(dragEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = dragEvent;
-                break;
-            }
-
-            case InputMessage::Type::TOUCH_MODE: {
-                TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
-                if (!touchModeEvent) return NO_MEMORY;
-
-                initializeTouchModeEvent(touchModeEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = touchModeEvent;
-                break;
-            }
-        }
-    }
-    return OK;
-}
-
-status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
-        nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
-    status_t result;
-    for (size_t i = mBatches.size(); i > 0; ) {
-        i--;
-        Batch& batch = mBatches[i];
-        if (frameTime < 0) {
-            result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent);
-            mBatches.erase(mBatches.begin() + i);
-            return result;
-        }
-
-        nsecs_t sampleTime = frameTime;
-        if (mResampleTouch) {
-            sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count();
-        }
-        ssize_t split = findSampleNoLaterThan(batch, sampleTime);
-        if (split < 0) {
-            continue;
-        }
-
-        result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
-        const InputMessage* next;
-        if (batch.samples.empty()) {
-            mBatches.erase(mBatches.begin() + i);
-            next = nullptr;
-        } else {
-            next = &batch.samples[0];
-        }
-        if (!result && mResampleTouch) {
-            resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
-        }
-        return result;
-    }
-
-    return WOULD_BLOCK;
-}
-
-status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory,
-        Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) {
-    MotionEvent* motionEvent = factory->createMotionEvent();
-    if (! motionEvent) return NO_MEMORY;
-
-    uint32_t chain = 0;
-    for (size_t i = 0; i < count; i++) {
-        InputMessage& msg = batch.samples[i];
-        updateTouchState(msg);
-        if (i) {
-            SeqChain seqChain;
-            seqChain.seq = msg.header.seq;
-            seqChain.chain = chain;
-            mSeqChains.push_back(seqChain);
-            addSample(motionEvent, &msg);
-        } else {
-            initializeMotionEvent(motionEvent, &msg);
-        }
-        chain = msg.header.seq;
-    }
-    batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
-
-    *outSeq = chain;
-    *outEvent = motionEvent;
-    return OK;
-}
-
-void InputConsumer::updateTouchState(InputMessage& msg) {
-    if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) {
-        return;
-    }
-
-    int32_t deviceId = msg.body.motion.deviceId;
-    int32_t source = msg.body.motion.source;
-
-    // Update the touch state history to incorporate the new input message.
-    // If the message is in the past relative to the most recently produced resampled
-    // touch, then use the resampled time and coordinates instead.
-    switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) {
-    case AMOTION_EVENT_ACTION_DOWN: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index < 0) {
-            mTouchStates.push_back({});
-            index = mTouchStates.size() - 1;
-        }
-        TouchState& touchState = mTouchStates[index];
-        touchState.initialize(deviceId, source);
-        touchState.addHistory(msg);
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_MOVE: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            touchState.addHistory(msg);
-            rewriteMessage(touchState, msg);
-        }
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_POINTER_DOWN: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
-            rewriteMessage(touchState, msg);
-        }
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_POINTER_UP: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            rewriteMessage(touchState, msg);
-            touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
-        }
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_SCROLL: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            rewriteMessage(touchState, msg);
-        }
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_UP:
-    case AMOTION_EVENT_ACTION_CANCEL: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            rewriteMessage(touchState, msg);
-            mTouchStates.erase(mTouchStates.begin() + index);
-        }
-        break;
-    }
-    }
-}
-
-/**
- * Replace the coordinates in msg with the coordinates in lastResample, if necessary.
- *
- * If lastResample is no longer valid for a specific pointer (i.e. the lastResample time
- * is in the past relative to msg and the past two events do not contain identical coordinates),
- * then invalidate the lastResample data for that pointer.
- * If the two past events have identical coordinates, then lastResample data for that pointer will
- * remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is
- * resampled to the new value x1, then x1 will always be used to replace x0 until some new value
- * not equal to x0 is received.
- */
-void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
-    nsecs_t eventTime = msg.body.motion.eventTime;
-    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
-        uint32_t id = msg.body.motion.pointers[i].properties.id;
-        if (state.lastResample.idBits.hasBit(id)) {
-            if (eventTime < state.lastResample.eventTime ||
-                    state.recentCoordinatesAreIdentical(id)) {
-                PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
-                const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
-                ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
-                         resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
-                         msgCoords.getY());
-                msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
-                msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
-                msgCoords.isResampled = true;
-            } else {
-                state.lastResample.idBits.clearBit(id);
-            }
-        }
-    }
-}
-
-void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
-    const InputMessage* next) {
-    if (!mResampleTouch
-            || !(isPointerEvent(event->getSource()))
-            || event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
-        return;
-    }
-
-    ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
-    if (index < 0) {
-        ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
-        return;
-    }
-
-    TouchState& touchState = mTouchStates[index];
-    if (touchState.historySize < 1) {
-        ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
-        return;
-    }
-
-    // Ensure that the current sample has all of the pointers that need to be reported.
-    const History* current = touchState.getHistory(0);
-    size_t pointerCount = event->getPointerCount();
-    for (size_t i = 0; i < pointerCount; i++) {
-        uint32_t id = event->getPointerId(i);
-        if (!current->idBits.hasBit(id)) {
-            ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
-            return;
-        }
-    }
-
-    // Find the data to use for resampling.
-    const History* other;
-    History future;
-    float alpha;
-    if (next) {
-        // Interpolate between current sample and future sample.
-        // So current->eventTime <= sampleTime <= future.eventTime.
-        future.initializeFrom(*next);
-        other = &future;
-        nsecs_t delta = future.eventTime - current->eventTime;
-        if (delta < RESAMPLE_MIN_DELTA) {
-            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
-                     delta);
-            return;
-        }
-        alpha = float(sampleTime - current->eventTime) / delta;
-    } else if (touchState.historySize >= 2) {
-        // Extrapolate future sample using current sample and past sample.
-        // So other->eventTime <= current->eventTime <= sampleTime.
-        other = touchState.getHistory(1);
-        nsecs_t delta = current->eventTime - other->eventTime;
-        if (delta < RESAMPLE_MIN_DELTA) {
-            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
-                     delta);
-            return;
-        } else if (delta > RESAMPLE_MAX_DELTA) {
-            ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
-                     delta);
-            return;
-        }
-        nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
-        if (sampleTime > maxPredict) {
-            ALOGD_IF(debugResampling(),
-                     "Sample time is too far in the future, adjusting prediction "
-                     "from %" PRId64 " to %" PRId64 " ns.",
-                     sampleTime - current->eventTime, maxPredict - current->eventTime);
-            sampleTime = maxPredict;
-        }
-        alpha = float(current->eventTime - sampleTime) / delta;
-    } else {
-        ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
-        return;
-    }
-
-    if (current->eventTime == sampleTime) {
-        // Prevents having 2 events with identical times and coordinates.
-        return;
-    }
-
-    // Resample touch coordinates.
-    History oldLastResample;
-    oldLastResample.initializeFrom(touchState.lastResample);
-    touchState.lastResample.eventTime = sampleTime;
-    touchState.lastResample.idBits.clear();
-    for (size_t i = 0; i < pointerCount; i++) {
-        uint32_t id = event->getPointerId(i);
-        touchState.lastResample.idToIndex[id] = i;
-        touchState.lastResample.idBits.markBit(id);
-        if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) {
-            // We maintain the previously resampled value for this pointer (stored in
-            // oldLastResample) when the coordinates for this pointer haven't changed since then.
-            // This way we don't introduce artificial jitter when pointers haven't actually moved.
-            // The isResampled flag isn't cleared as the values don't reflect what the device is
-            // actually reporting.
-
-            // We know here that the coordinates for the pointer haven't changed because we
-            // would've cleared the resampled bit in rewriteMessage if they had. We can't modify
-            // lastResample in place becasue the mapping from pointer ID to index may have changed.
-            touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
-            continue;
-        }
-
-        PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
-        const PointerCoords& currentCoords = current->getPointerById(id);
-        resampledCoords = currentCoords;
-        resampledCoords.isResampled = true;
-        if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
-            const PointerCoords& otherCoords = other->getPointerById(id);
-            resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
-                                         lerp(currentCoords.getX(), otherCoords.getX(), alpha));
-            resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                         lerp(currentCoords.getY(), otherCoords.getY(), alpha));
-            ALOGD_IF(debugResampling(),
-                     "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
-                     "other (%0.3f, %0.3f), alpha %0.3f",
-                     id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
-                     currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
-        } else {
-            ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
-                     resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
-                     currentCoords.getY());
-        }
-    }
-
-    event->addSample(sampleTime, touchState.lastResample.pointers);
-}
-
-status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
-    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-             "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
-             mChannel->getName().c_str(), seq, toString(handled));
-
-    if (!seq) {
-        ALOGE("Attempted to send a finished signal with sequence number 0.");
-        return BAD_VALUE;
-    }
-
-    // Send finished signals for the batch sequence chain first.
-    size_t seqChainCount = mSeqChains.size();
-    if (seqChainCount) {
-        uint32_t currentSeq = seq;
-        uint32_t chainSeqs[seqChainCount];
-        size_t chainIndex = 0;
-        for (size_t i = seqChainCount; i > 0; ) {
-             i--;
-             const SeqChain& seqChain = mSeqChains[i];
-             if (seqChain.seq == currentSeq) {
-                 currentSeq = seqChain.chain;
-                 chainSeqs[chainIndex++] = currentSeq;
-                 mSeqChains.erase(mSeqChains.begin() + i);
-             }
-        }
-        status_t status = OK;
-        while (!status && chainIndex > 0) {
-            chainIndex--;
-            status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
-        }
-        if (status) {
-            // An error occurred so at least one signal was not sent, reconstruct the chain.
-            for (;;) {
-                SeqChain seqChain;
-                seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
-                seqChain.chain = chainSeqs[chainIndex];
-                mSeqChains.push_back(seqChain);
-                if (!chainIndex) break;
-                chainIndex--;
-            }
-            return status;
-        }
-    }
-
-    // Send finished signal for the last message in the batch.
-    return sendUnchainedFinishedSignal(seq, handled);
-}
-
-status_t InputConsumer::sendTimeline(int32_t inputEventId,
-                                     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
-    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-             "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
-             ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
-             mChannel->getName().c_str(), inputEventId,
-             graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
-             graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
-
-    InputMessage msg;
-    msg.header.type = InputMessage::Type::TIMELINE;
-    msg.header.seq = 0;
-    msg.body.timeline.eventId = inputEventId;
-    msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline);
-    return mChannel->sendMessage(&msg);
-}
-
-nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const {
-    auto it = mConsumeTimes.find(seq);
-    // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
-    // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
-    LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
-                        seq);
-    return it->second;
-}
-
-void InputConsumer::popConsumeTime(uint32_t seq) {
-    mConsumeTimes.erase(seq);
-}
-
-status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
-    InputMessage msg;
-    msg.header.type = InputMessage::Type::FINISHED;
-    msg.header.seq = seq;
-    msg.body.finished.handled = handled;
-    msg.body.finished.consumeTime = getConsumeTime(seq);
-    status_t result = mChannel->sendMessage(&msg);
-    if (result == OK) {
-        // Remove the consume time if the socket write succeeded. We will not need to ack this
-        // message anymore. If the socket write did not succeed, we will try again and will still
-        // need consume time.
-        popConsumeTime(seq);
-    }
-    return result;
-}
-
-bool InputConsumer::hasPendingBatch() const {
-    return !mBatches.empty();
-}
-
-int32_t InputConsumer::getPendingBatchSource() const {
-    if (mBatches.empty()) {
-        return AINPUT_SOURCE_CLASS_NONE;
-    }
-
-    const Batch& batch = mBatches[0];
-    const InputMessage& head = batch.samples[0];
-    return head.body.motion.source;
-}
-
-ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
-    for (size_t i = 0; i < mBatches.size(); i++) {
-        const Batch& batch = mBatches[i];
-        const InputMessage& head = batch.samples[0];
-        if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
-    for (size_t i = 0; i < mTouchStates.size(); i++) {
-        const TouchState& touchState = mTouchStates[i];
-        if (touchState.deviceId == deviceId && touchState.source == source) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.key.eventId, msg->body.key.deviceId, msg->body.key.source,
-                      msg->body.key.displayId, msg->body.key.hmac, msg->body.key.action,
-                      msg->body.key.flags, msg->body.key.keyCode, msg->body.key.scanCode,
-                      msg->body.key.metaState, msg->body.key.repeatCount, msg->body.key.downTime,
-                      msg->body.key.eventTime);
-}
-
-void InputConsumer::initializeFocusEvent(FocusEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus);
-}
-
-void InputConsumer::initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.capture.eventId, msg->body.capture.pointerCaptureEnabled);
-}
-
-void InputConsumer::initializeDragEvent(DragEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.drag.eventId, msg->body.drag.x, msg->body.drag.y,
-                      msg->body.drag.isExiting);
-}
-
-void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
-    uint32_t pointerCount = msg->body.motion.pointerCount;
-    PointerProperties pointerProperties[pointerCount];
-    PointerCoords pointerCoords[pointerCount];
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        pointerProperties[i] = msg->body.motion.pointers[i].properties;
-        pointerCoords[i] = msg->body.motion.pointers[i].coords;
-    }
-
-    ui::Transform transform;
-    transform.set({msg->body.motion.dsdx, msg->body.motion.dtdx, msg->body.motion.tx,
-                   msg->body.motion.dtdy, msg->body.motion.dsdy, msg->body.motion.ty, 0, 0, 1});
-    ui::Transform displayTransform;
-    displayTransform.set({msg->body.motion.dsdxRaw, msg->body.motion.dtdxRaw,
-                          msg->body.motion.txRaw, msg->body.motion.dtdyRaw,
-                          msg->body.motion.dsdyRaw, msg->body.motion.tyRaw, 0, 0, 1});
-    event->initialize(msg->body.motion.eventId, msg->body.motion.deviceId, msg->body.motion.source,
-                      msg->body.motion.displayId, msg->body.motion.hmac, msg->body.motion.action,
-                      msg->body.motion.actionButton, msg->body.motion.flags,
-                      msg->body.motion.edgeFlags, msg->body.motion.metaState,
-                      msg->body.motion.buttonState, msg->body.motion.classification, transform,
-                      msg->body.motion.xPrecision, msg->body.motion.yPrecision,
-                      msg->body.motion.xCursorPosition, msg->body.motion.yCursorPosition,
-                      displayTransform, msg->body.motion.downTime, msg->body.motion.eventTime,
-                      pointerCount, pointerProperties, pointerCoords);
-}
-
-void InputConsumer::initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.touchMode.eventId, msg->body.touchMode.isInTouchMode);
-}
-
-void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
-    uint32_t pointerCount = msg->body.motion.pointerCount;
-    PointerCoords pointerCoords[pointerCount];
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        pointerCoords[i] = msg->body.motion.pointers[i].coords;
-    }
-
-    event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
-    event->addSample(msg->body.motion.eventTime, pointerCoords);
-}
-
-bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) {
-    const InputMessage& head = batch.samples[0];
-    uint32_t pointerCount = msg->body.motion.pointerCount;
-    if (head.body.motion.pointerCount != pointerCount
-            || head.body.motion.action != msg->body.motion.action) {
-        return false;
-    }
-    for (size_t i = 0; i < pointerCount; i++) {
-        if (head.body.motion.pointers[i].properties
-                != msg->body.motion.pointers[i].properties) {
-            return false;
-        }
-    }
-    return true;
-}
-
-ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
-    size_t numSamples = batch.samples.size();
-    size_t index = 0;
-    while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) {
-        index += 1;
-    }
-    return ssize_t(index) - 1;
-}
-
-std::string InputConsumer::dump() const {
-    std::string out;
-    out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n";
-    out = out + "mChannel = " + mChannel->getName() + "\n";
-    out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
-    if (mMsgDeferred) {
-        out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n";
-    }
-    out += "Batches:\n";
-    for (const Batch& batch : mBatches) {
-        out += "    Batch:\n";
-        for (const InputMessage& msg : batch.samples) {
-            out += android::base::StringPrintf("        Message %" PRIu32 ": %s ", msg.header.seq,
-                                               ftl::enum_string(msg.header.type).c_str());
-            switch (msg.header.type) {
-                case InputMessage::Type::KEY: {
-                    out += android::base::StringPrintf("action=%s keycode=%" PRId32,
-                                                       KeyEvent::actionToString(
-                                                               msg.body.key.action),
-                                                       msg.body.key.keyCode);
-                    break;
-                }
-                case InputMessage::Type::MOTION: {
-                    out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action);
-                    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
-                        const float x = msg.body.motion.pointers[i].coords.getX();
-                        const float y = msg.body.motion.pointers[i].coords.getY();
-                        out += android::base::StringPrintf("\n            Pointer %" PRIu32
-                                                           " : x=%.1f y=%.1f",
-                                                           i, x, y);
-                    }
-                    break;
-                }
-                case InputMessage::Type::FINISHED: {
-                    out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64,
-                                                       toString(msg.body.finished.handled),
-                                                       msg.body.finished.consumeTime);
-                    break;
-                }
-                case InputMessage::Type::FOCUS: {
-                    out += android::base::StringPrintf("hasFocus=%s",
-                                                       toString(msg.body.focus.hasFocus));
-                    break;
-                }
-                case InputMessage::Type::CAPTURE: {
-                    out += android::base::StringPrintf("hasCapture=%s",
-                                                       toString(msg.body.capture
-                                                                        .pointerCaptureEnabled));
-                    break;
-                }
-                case InputMessage::Type::DRAG: {
-                    out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s",
-                                                       msg.body.drag.x, msg.body.drag.y,
-                                                       toString(msg.body.drag.isExiting));
-                    break;
-                }
-                case InputMessage::Type::TIMELINE: {
-                    const nsecs_t gpuCompletedTime =
-                            msg.body.timeline
-                                    .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
-                    const nsecs_t presentTime =
-                            msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
-                    out += android::base::StringPrintf("inputEventId=%" PRId32
-                                                       ", gpuCompletedTime=%" PRId64
-                                                       ", presentTime=%" PRId64,
-                                                       msg.body.timeline.eventId, gpuCompletedTime,
-                                                       presentTime);
-                    break;
-                }
-                case InputMessage::Type::TOUCH_MODE: {
-                    out += android::base::StringPrintf("isInTouchMode=%s",
-                                                       toString(msg.body.touchMode.isInTouchMode));
-                    break;
-                }
-            }
-            out += "\n";
-        }
-    }
-    if (mBatches.empty()) {
-        out += "    <empty>\n";
-    }
-    out += "mSeqChains:\n";
-    for (const SeqChain& chain : mSeqChains) {
-        out += android::base::StringPrintf("    chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq,
-                                           chain.chain);
-    }
-    if (mSeqChains.empty()) {
-        out += "    <empty>\n";
-    }
-    out += "mConsumeTimes:\n";
-    for (const auto& [seq, consumeTime] : mConsumeTimes) {
-        out += android::base::StringPrintf("    seq = %" PRIu32 " consumeTime = %" PRId64, seq,
-                                           consumeTime);
-    }
-    if (mConsumeTimes.empty()) {
-        out += "    <empty>\n";
-    }
-    return out;
-}
-
 } // namespace android
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index e2feabc..1cf5612 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -19,16 +19,13 @@
 #include <stdlib.h>
 #include <string.h>
 
-#ifdef __linux__
-#include <binder/Parcel.h>
-#endif
 #include <android/keycodes.h>
 #include <attestation/HmacKeyManager.h>
+#include <binder/Parcel.h>
 #include <input/InputEventLabels.h>
 #include <input/KeyCharacterMap.h>
 #include <input/Keyboard.h>
 
-#include <gui/constants.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
 #include <utils/Timers.h>
@@ -496,13 +493,14 @@
     return false;
 }
 
-void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents,
-        int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time) {
+void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents, int32_t deviceId, int32_t keyCode,
+                             int32_t metaState, bool down, nsecs_t time) {
     outEvents.push();
     KeyEvent& event = outEvents.editTop();
-    event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, 0, keyCode,
-                     0, metaState, 0, time, time);
+    event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC,
+                     down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, 0, keyCode, 0, metaState,
+                     0, time, time);
 }
 
 void KeyCharacterMap::addMetaKeys(Vector<KeyEvent>& outEvents,
@@ -612,7 +610,6 @@
     }
 }
 
-#ifdef __linux__
 std::unique_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
     if (parcel == nullptr) {
         ALOGE("%s: Null parcel", __func__);
@@ -745,7 +742,6 @@
         parcel->writeInt32(toAndroidKeyCode);
     }
 }
-#endif // __linux__
 
 // --- KeyCharacterMap::Parser ---
 
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index ab8c341..5088188 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -97,6 +97,10 @@
 
 bool kernelConfigsArePresent(const std::set<std::string>& configs) {
 #if defined(__ANDROID__)
+    if (configs.empty()) {
+        return true;
+    }
+
     std::map<std::string, std::string> kernelConfigs;
     const status_t result = android::kernelconfigs::LoadKernelConfigs(&kernelConfigs);
     LOG_ALWAYS_FATAL_IF(result != OK, "Kernel configs could not be fetched");
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index c4e3ff6..5b61d39 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -18,21 +18,29 @@
 
 #include <input/MotionPredictor.h>
 
+#include <algorithm>
+#include <array>
 #include <cinttypes>
 #include <cmath>
 #include <cstddef>
 #include <cstdint>
+#include <limits>
+#include <optional>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <android/input.h>
+#include <com_android_input_flags.h>
 
 #include <attestation/HmacKeyManager.h>
 #include <ftl/enum.h>
 #include <input/TfLiteMotionPredictor.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 namespace {
 
@@ -55,8 +63,73 @@
     return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta};
 }
 
+float normalizeRange(float x, float min, float max) {
+    const float normalized = (x - min) / (max - min);
+    return std::min(1.0f, std::max(0.0f, normalized));
+}
+
 } // namespace
 
+// --- JerkTracker ---
+
+JerkTracker::JerkTracker(bool normalizedDt) : mNormalizedDt(normalizedDt) {}
+
+void JerkTracker::pushSample(int64_t timestamp, float xPos, float yPos) {
+    mTimestamps.pushBack(timestamp);
+    const int numSamples = mTimestamps.size();
+
+    std::array<float, 4> newXDerivatives;
+    std::array<float, 4> newYDerivatives;
+
+    /**
+     * Diagram showing the calculation of higher order derivatives of sample x3
+     * collected at time=t3.
+     * Terms in parentheses are not stored (and not needed for calculations)
+     *  t0 ----- t1  ----- t2 ----- t3
+     * (x0)-----(x1) ----- x2 ----- x3
+     * (x'0) --- x'1 ---  x'2
+     *  x''0  -  x''1
+     *  x'''0
+     *
+     * In this example:
+     * x'2 = (x3 - x2) / (t3 - t2)
+     * x''1 = (x'2 - x'1) / (t2 - t1)
+     * x'''0 = (x''1 - x''0) / (t1 - t0)
+     * Therefore, timestamp history is needed to calculate higher order derivatives,
+     * compared to just the last calculated derivative sample.
+     *
+     * If mNormalizedDt = true, then dt = 1 and the division is moot.
+     */
+    for (int i = 0; i < numSamples; ++i) {
+        if (i == 0) {
+            newXDerivatives[i] = xPos;
+            newYDerivatives[i] = yPos;
+        } else {
+            newXDerivatives[i] = newXDerivatives[i - 1] - mXDerivatives[i - 1];
+            newYDerivatives[i] = newYDerivatives[i - 1] - mYDerivatives[i - 1];
+            if (!mNormalizedDt) {
+                const float dt = mTimestamps[numSamples - i] - mTimestamps[numSamples - i - 1];
+                newXDerivatives[i] = newXDerivatives[i] / dt;
+                newYDerivatives[i] = newYDerivatives[i] / dt;
+            }
+        }
+    }
+
+    std::swap(newXDerivatives, mXDerivatives);
+    std::swap(newYDerivatives, mYDerivatives);
+}
+
+void JerkTracker::reset() {
+    mTimestamps.clear();
+}
+
+std::optional<float> JerkTracker::jerkMagnitude() const {
+    if (mTimestamps.size() == mTimestamps.capacity()) {
+        return std::hypot(mXDerivatives[3], mYDerivatives[3]);
+    }
+    return std::nullopt;
+}
+
 // --- MotionPredictor ---
 
 MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
@@ -103,6 +176,7 @@
     if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) {
         ALOGD_IF(isDebug(), "End of event stream");
         mBuffers->reset();
+        mJerkTracker.reset();
         mLastEvent.reset();
         return {};
     } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) {
@@ -137,6 +211,9 @@
                                                                           0, i),
                                      .orientation = event.getHistoricalOrientation(0, i),
                              });
+        mJerkTracker.pushSample(event.getHistoricalEventTime(i),
+                                coords->getAxisValue(AMOTION_EVENT_AXIS_X),
+                                coords->getAxisValue(AMOTION_EVENT_AXIS_Y));
     }
 
     if (!mLastEvent) {
@@ -184,6 +261,17 @@
     int64_t predictionTime = mBuffers->lastTimestamp();
     const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
 
+    const float jerkMagnitude = mJerkTracker.jerkMagnitude().value_or(0);
+    const float fractionKept =
+            1 - normalizeRange(jerkMagnitude, mModel->config().lowJerk, mModel->config().highJerk);
+    // float to ensure proper division below.
+    const float predictionTimeWindow = futureTime - predictionTime;
+    const int maxNumPredictions = static_cast<int>(
+            std::ceil(predictionTimeWindow / mModel->config().predictionInterval * fractionKept));
+    ALOGD_IF(isDebug(),
+             "jerk (d^3p/normalizedDt^3): %f, fraction of prediction window pruned: %f, max number "
+             "of predictions: %d",
+             jerkMagnitude, 1 - fractionKept, maxNumPredictions);
     for (size_t i = 0; i < static_cast<size_t>(predictedR.size()) && predictionTime <= futureTime;
          ++i) {
         if (predictedR[i] < mModel->config().distanceNoiseFloor) {
@@ -197,7 +285,13 @@
             // device starts to speed up, but avoids producing noisy predictions as it slows down.
             break;
         }
-        // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+        if (input_flags::enable_prediction_pruning_via_jerk_thresholding()) {
+            if (i >= static_cast<size_t>(maxNumPredictions)) {
+                break;
+            }
+        }
+        // TODO(b/266747654): Stop predictions if confidence is < some
+        // threshold. Currently predictions are pruned via jerk thresholding.
 
         const TfLiteMotionPredictorSample::Point predictedPoint =
                 convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
index 0412d08..ccf018e 100644
--- a/libs/input/MotionPredictorMetricsManager.cpp
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -21,14 +21,13 @@
 #include <algorithm>
 
 #include <android-base/logging.h>
+#ifdef __ANDROID__
+#include <statslog_libinput.h>
+#endif // __ANDROID__
 
 #include "Eigen/Core"
 #include "Eigen/Geometry"
 
-#ifdef __ANDROID__
-#include <statslog_libinput.h>
-#endif
-
 namespace android {
 namespace {
 
@@ -48,22 +47,20 @@
 
 void MotionPredictorMetricsManager::defaultReportAtomFunction(
         const MotionPredictorMetricsManager::AtomFields& atomFields) {
-    // Call stats_write logging function only on Android targets (not supported on host).
 #ifdef __ANDROID__
-    android::stats::libinput::
-            stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
-                            /*stylus_vendor_id=*/0,
-                            /*stylus_product_id=*/0,
-                            atomFields.deltaTimeBucketMilliseconds,
-                            atomFields.alongTrajectoryErrorMeanMillipixels,
-                            atomFields.alongTrajectoryErrorStdMillipixels,
-                            atomFields.offTrajectoryRmseMillipixels,
-                            atomFields.pressureRmseMilliunits,
-                            atomFields.highVelocityAlongTrajectoryRmse,
-                            atomFields.highVelocityOffTrajectoryRmse,
-                            atomFields.scaleInvariantAlongTrajectoryRmse,
-                            atomFields.scaleInvariantOffTrajectoryRmse);
-#endif
+    android::libinput::stats_write(android::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
+                                   /*stylus_vendor_id=*/0,
+                                   /*stylus_product_id=*/0,
+                                   atomFields.deltaTimeBucketMilliseconds,
+                                   atomFields.alongTrajectoryErrorMeanMillipixels,
+                                   atomFields.alongTrajectoryErrorStdMillipixels,
+                                   atomFields.offTrajectoryRmseMillipixels,
+                                   atomFields.pressureRmseMilliunits,
+                                   atomFields.highVelocityAlongTrajectoryRmse,
+                                   atomFields.highVelocityOffTrajectoryRmse,
+                                   atomFields.scaleInvariantAlongTrajectoryRmse,
+                                   atomFields.scaleInvariantOffTrajectoryRmse);
+#endif // __ANDROID__
 }
 
 MotionPredictorMetricsManager::MotionPredictorMetricsManager(
@@ -113,7 +110,12 @@
 // Adds new predictions to mRecentPredictions and maintains the invariant that elements are
 // sorted in ascending order of targetTimestamp.
 void MotionPredictorMetricsManager::onPredict(const MotionEvent& predictionEvent) {
-    for (size_t i = 0; i < predictionEvent.getHistorySize() + 1; ++i) {
+    const size_t numPredictions = predictionEvent.getHistorySize() + 1;
+    if (numPredictions > mMaxNumPredictions) {
+        LOG(WARNING) << "numPredictions (" << numPredictions << ") > mMaxNumPredictions ("
+                     << mMaxNumPredictions << "). Ignoring extra predictions in metrics.";
+    }
+    for (size_t i = 0; (i < numPredictions) && (i < mMaxNumPredictions); ++i) {
         // Convert MotionEvent to PredictionPoint.
         const PointerCoords* coords =
                 predictionEvent.getHistoricalRawPointerCoords(/*pointerIndex=*/0, i);
@@ -325,42 +327,44 @@
             mAtomFields[i].highVelocityOffTrajectoryRmse =
                     static_cast<int>(offTrajectoryRmse * 1000);
         }
+    }
 
-        // Scale-invariant errors: reported only for the last time bucket, where the values
-        // represent an average across all time buckets.
-        if (i + 1 == mMaxNumPredictions) {
-            // Compute error averages.
-            float alongTrajectoryRmseSum = 0;
-            float offTrajectoryRmseSum = 0;
-            for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) {
-                // If we have general errors (checked above), we should always also have
-                // scale-invariant errors.
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantErrorsCount == 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantErrorsCount is 0", j);
-
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f "
-                                    "should not be negative",
-                                    j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse);
-                alongTrajectoryRmseSum +=
-                        std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse /
-                                  mAggregatedMetrics[j].scaleInvariantErrorsCount);
-
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f "
-                                    "should not be negative",
-                                    j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse);
-                offTrajectoryRmseSum +=
-                        std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse /
-                                  mAggregatedMetrics[j].scaleInvariantErrorsCount);
+    // Scale-invariant errors: the average scale-invariant error across all time buckets
+    // is reported in the last time bucket.
+    {
+        // Compute error averages.
+        float alongTrajectoryRmseSum = 0;
+        float offTrajectoryRmseSum = 0;
+        int bucket_count = 0;
+        for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) {
+            if (mAggregatedMetrics[j].scaleInvariantErrorsCount == 0) {
+                continue;
             }
 
-            const float averageAlongTrajectoryRmse =
-                    alongTrajectoryRmseSum / mAggregatedMetrics.size();
+            LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0,
+                                "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f "
+                                "should not be negative",
+                                j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse);
+            alongTrajectoryRmseSum +=
+                    std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse /
+                              mAggregatedMetrics[j].scaleInvariantErrorsCount);
+
+            LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0,
+                                "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f "
+                                "should not be negative",
+                                j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse);
+            offTrajectoryRmseSum += std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse /
+                                              mAggregatedMetrics[j].scaleInvariantErrorsCount);
+
+            ++bucket_count;
+        }
+
+        if (bucket_count > 0) {
+            const float averageAlongTrajectoryRmse = alongTrajectoryRmseSum / bucket_count;
             mAtomFields.back().scaleInvariantAlongTrajectoryRmse =
                     static_cast<int>(averageAlongTrajectoryRmse * 1000);
 
-            const float averageOffTrajectoryRmse = offTrajectoryRmseSum / mAggregatedMetrics.size();
+            const float averageOffTrajectoryRmse = offTrajectoryRmseSum / bucket_count;
             mAtomFields.back().scaleInvariantOffTrajectoryRmse =
                     static_cast<int>(averageOffTrajectoryRmse * 1000);
         }
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
index d17476e..b843a4b 100644
--- a/libs/input/TfLiteMotionPredictor.cpp
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -281,6 +281,8 @@
     Config config{
             .predictionInterval = parseXMLInt64(*configRoot, "prediction-interval"),
             .distanceNoiseFloor = parseXMLFloat(*configRoot, "distance-noise-floor"),
+            .lowJerk = parseXMLFloat(*configRoot, "low-jerk"),
+            .highJerk = parseXMLFloat(*configRoot, "high-jerk"),
     };
 
     return std::unique_ptr<TfLiteMotionPredictorModel>(
diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp
index c835a08..edd31e9 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -15,7 +15,6 @@
  */
 
 #define LOG_TAG "VelocityControl"
-//#define LOG_NDEBUG 0
 
 // Log debug messages about acceleration.
 static constexpr bool DEBUG_ACCELERATION = false;
@@ -23,6 +22,7 @@
 #include <math.h>
 #include <limits.h>
 
+#include <android-base/logging.h>
 #include <input/VelocityControl.h>
 #include <utils/BitSet.h>
 #include <utils/Timers.h>
@@ -37,15 +37,6 @@
     reset();
 }
 
-const VelocityControlParameters& VelocityControl::getParameters() const{
-    return mParameters;
-}
-
-void VelocityControl::setParameters(const VelocityControlParameters& parameters) {
-    mParameters = parameters;
-    reset();
-}
-
 void VelocityControl::reset() {
     mLastMovementTime = LLONG_MIN;
     mRawPositionX = 0;
@@ -54,65 +45,156 @@
 }
 
 void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) {
-    if ((deltaX && *deltaX) || (deltaY && *deltaY)) {
-        if (eventTime >= mLastMovementTime + STOP_TIME) {
-            if (DEBUG_ACCELERATION && mLastMovementTime != LLONG_MIN) {
-                ALOGD("VelocityControl: stopped, last movement was %0.3fms ago",
-                           (eventTime - mLastMovementTime) * 0.000001f);
-            }
-            reset();
+    if ((deltaX == nullptr || *deltaX == 0) && (deltaY == nullptr || *deltaY == 0)) {
+        return;
+    }
+    if (eventTime >= mLastMovementTime + STOP_TIME) {
+        ALOGD_IF(DEBUG_ACCELERATION && mLastMovementTime != LLONG_MIN,
+                 "VelocityControl: stopped, last movement was %0.3fms ago",
+                 (eventTime - mLastMovementTime) * 0.000001f);
+        reset();
+    }
+
+    mLastMovementTime = eventTime;
+    if (deltaX) {
+        mRawPositionX += *deltaX;
+    }
+    if (deltaY) {
+        mRawPositionY += *deltaY;
+    }
+    mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X, mRawPositionX);
+    mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y, mRawPositionY);
+    scaleDeltas(deltaX, deltaY);
+}
+
+// --- SimpleVelocityControl ---
+
+const VelocityControlParameters& SimpleVelocityControl::getParameters() const {
+    return mParameters;
+}
+
+void SimpleVelocityControl::setParameters(const VelocityControlParameters& parameters) {
+    mParameters = parameters;
+    reset();
+}
+
+void SimpleVelocityControl::scaleDeltas(float* deltaX, float* deltaY) {
+    std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
+    std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
+    float scale = mParameters.scale;
+    if (vx.has_value() && vy.has_value()) {
+        float speed = hypotf(*vx, *vy) * scale;
+        if (speed >= mParameters.highThreshold) {
+            // Apply full acceleration above the high speed threshold.
+            scale *= mParameters.acceleration;
+        } else if (speed > mParameters.lowThreshold) {
+            // Linearly interpolate the acceleration to apply between the low and high
+            // speed thresholds.
+            scale *= 1 +
+                    (speed - mParameters.lowThreshold) /
+                            (mParameters.highThreshold - mParameters.lowThreshold) *
+                            (mParameters.acceleration - 1);
         }
 
-        mLastMovementTime = eventTime;
-        if (deltaX) {
-            mRawPositionX += *deltaX;
-        }
-        if (deltaY) {
-            mRawPositionY += *deltaY;
-        }
-        mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X,
-                                     mRawPositionX);
-        mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y,
-                                     mRawPositionY);
+        ALOGD_IF(DEBUG_ACCELERATION,
+                 "SimpleVelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
+                 "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
+                 mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+                 mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale);
 
-        std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
-        std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
-        float scale = mParameters.scale;
-        if (vx && vy) {
-            float speed = hypotf(*vx, *vy) * scale;
-            if (speed >= mParameters.highThreshold) {
-                // Apply full acceleration above the high speed threshold.
-                scale *= mParameters.acceleration;
-            } else if (speed > mParameters.lowThreshold) {
-                // Linearly interpolate the acceleration to apply between the low and high
-                // speed thresholds.
-                scale *= 1 + (speed - mParameters.lowThreshold)
-                        / (mParameters.highThreshold - mParameters.lowThreshold)
-                        * (mParameters.acceleration - 1);
-            }
+    } else {
+        ALOGD_IF(DEBUG_ACCELERATION,
+                 "SimpleVelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
+                 mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+                 mParameters.acceleration);
+    }
 
-            if (DEBUG_ACCELERATION) {
-                ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
-                      "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
-                      mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
-                      mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale);
-            }
+    if (deltaX != nullptr) {
+        *deltaX *= scale;
+    }
+    if (deltaY != nullptr) {
+        *deltaY *= scale;
+    }
+}
 
-        } else {
-            if (DEBUG_ACCELERATION) {
-                ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
-                        mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
-                        mParameters.acceleration);
-            }
-        }
+// --- CurvedVelocityControl ---
 
-        if (deltaX) {
-            *deltaX *= scale;
-        }
-        if (deltaY) {
-            *deltaY *= scale;
+namespace {
+
+/**
+ * The resolution that we assume a mouse to have, in counts per inch.
+ *
+ * Mouse resolutions vary wildly, but 800 CPI is probably the most common. There should be enough
+ * range in the available sensitivity settings to accommodate users of mice with other resolutions.
+ */
+constexpr int32_t MOUSE_CPI = 800;
+
+float countsToMm(float counts) {
+    return counts / MOUSE_CPI * 25.4;
+}
+
+} // namespace
+
+CurvedVelocityControl::CurvedVelocityControl()
+      : mCurveSegments(createAccelerationCurveForPointerSensitivity(0)) {}
+
+void CurvedVelocityControl::setCurve(const std::vector<AccelerationCurveSegment>& curve) {
+    mCurveSegments = curve;
+}
+
+void CurvedVelocityControl::setAccelerationEnabled(bool enabled) {
+    mAccelerationEnabled = enabled;
+}
+
+void CurvedVelocityControl::scaleDeltas(float* deltaX, float* deltaY) {
+    if (!mAccelerationEnabled) {
+        ALOGD_IF(DEBUG_ACCELERATION, "CurvedVelocityControl: acceleration disabled");
+        return;
+    }
+
+    std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
+    std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
+
+    float ratio;
+    if (vx.has_value() && vy.has_value()) {
+        float vxMmPerS = countsToMm(*vx);
+        float vyMmPerS = countsToMm(*vy);
+        float speedMmPerS = sqrtf(vxMmPerS * vxMmPerS + vyMmPerS * vyMmPerS);
+
+        const AccelerationCurveSegment& seg = segmentForSpeed(speedMmPerS);
+        ratio = seg.baseGain + seg.reciprocal / speedMmPerS;
+        ALOGD_IF(DEBUG_ACCELERATION,
+                 "CurvedVelocityControl: velocities (%0.3f, %0.3f) → speed %0.3f → ratio %0.3f",
+                 vxMmPerS, vyMmPerS, speedMmPerS, ratio);
+    } else {
+        // We don't have enough data to compute a velocity yet. This happens early in the movement,
+        // when the speed is presumably low, so use the base gain of the first segment of the curve.
+        // (This would behave oddly for curves with a reciprocal term on the first segment, but we
+        // don't have any of those, and they'd be very strange at velocities close to zero anyway.)
+        ratio = mCurveSegments[0].baseGain;
+        ALOGD_IF(DEBUG_ACCELERATION,
+                 "CurvedVelocityControl: unknown velocity, using base gain of first segment (%.3f)",
+                 ratio);
+    }
+
+    if (deltaX != nullptr) {
+        *deltaX *= ratio;
+    }
+    if (deltaY != nullptr) {
+        *deltaY *= ratio;
+    }
+}
+
+const AccelerationCurveSegment& CurvedVelocityControl::segmentForSpeed(float speedMmPerS) {
+    for (const AccelerationCurveSegment& seg : mCurveSegments) {
+        if (speedMmPerS <= seg.maxPointerSpeedMmPerS) {
+            return seg;
         }
     }
+    ALOGE("CurvedVelocityControl: No segment found for speed %.3f; last segment should always have "
+          "a max speed of infinity.",
+          speedMmPerS);
+    return mCurveSegments.back();
 }
 
 } // namespace android
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index db7031a..eea06f1 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -39,7 +39,9 @@
 }
 
 namespace android {
+
 VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {}
+
 VirtualInputDevice::~VirtualInputDevice() {
     ioctl(mFd, UI_DEV_DESTROY);
 }
@@ -56,7 +58,7 @@
     return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev);
 }
 
-/** Utility method to write keyboard key events or mouse button events. */
+/** Utility method to write keyboard key events or mouse/stylus button events. */
 bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAction,
                                          const std::map<int, int>& evKeyCodeMapping,
                                          const std::map<int, UinputAction>& actionMapping,
@@ -68,13 +70,17 @@
     }
     auto actionIterator = actionMapping.find(androidAction);
     if (actionIterator == actionMapping.end()) {
+        ALOGE("Unsupported native action for android action %d", androidAction);
         return false;
     }
-    if (!writeInputEvent(EV_KEY, static_cast<uint16_t>(evKeyCodeIterator->second),
-                         static_cast<int32_t>(actionIterator->second), eventTime)) {
+    int32_t action = static_cast<int32_t>(actionIterator->second);
+    uint16_t evKeyCode = static_cast<uint16_t>(evKeyCodeIterator->second);
+    if (!writeInputEvent(EV_KEY, evKeyCode, action, eventTime)) {
+        ALOGE("Failed to write native action %d and EV keycode %u.", action, evKeyCode);
         return false;
     }
     if (!writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime)) {
+        ALOGE("Failed to write SYN_REPORT for EV_KEY event.");
         return false;
     }
     return true;
@@ -85,6 +91,7 @@
         {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS},
         {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE},
 };
+
 // Keycode mapping from https://source.android.com/devices/input/keyboard-devices
 const std::map<int, int> VirtualKeyboard::KEY_CODE_MAPPING = {
         {AKEYCODE_0, KEY_0},
@@ -195,7 +202,9 @@
         {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
         {AKEYCODE_LANGUAGE_SWITCH, KEY_LANGUAGE},
 };
+
 VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+
 VirtualKeyboard::~VirtualKeyboard() {}
 
 bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction,
@@ -275,6 +284,7 @@
         {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE},
         {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL},
 };
+
 // Tool type mapping from https://source.android.com/devices/input/touch-devices
 const std::map<int, int> VirtualTouchscreen::TOOL_TYPE_MAPPING = {
         {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER},
@@ -393,4 +403,110 @@
     return true;
 }
 
+// --- VirtualStylus ---
+const std::map<int, int> VirtualStylus::TOOL_TYPE_MAPPING = {
+        {AMOTION_EVENT_TOOL_TYPE_STYLUS, BTN_TOOL_PEN},
+        {AMOTION_EVENT_TOOL_TYPE_ERASER, BTN_TOOL_RUBBER},
+};
+
+// Button code mapping from https://source.android.com/devices/input/touch-devices
+const std::map<int, int> VirtualStylus::BUTTON_CODE_MAPPING = {
+        {AMOTION_EVENT_BUTTON_STYLUS_PRIMARY, BTN_STYLUS},
+        {AMOTION_EVENT_BUTTON_STYLUS_SECONDARY, BTN_STYLUS2},
+};
+
+VirtualStylus::VirtualStylus(unique_fd fd)
+      : VirtualInputDevice(std::move(fd)), mIsStylusDown(false) {}
+
+VirtualStylus::~VirtualStylus() {}
+
+bool VirtualStylus::writeMotionEvent(int32_t toolType, int32_t action, int32_t locationX,
+                                     int32_t locationY, int32_t pressure, int32_t tiltX,
+                                     int32_t tiltY, std::chrono::nanoseconds eventTime) {
+    auto actionIterator = VirtualTouchscreen::TOUCH_ACTION_MAPPING.find(action);
+    if (actionIterator == VirtualTouchscreen::TOUCH_ACTION_MAPPING.end()) {
+        ALOGE("Unsupported action passed for stylus: %d.", action);
+        return false;
+    }
+    UinputAction uinputAction = actionIterator->second;
+    auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType);
+    if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) {
+        ALOGE("Unsupported tool type passed for stylus: %d.", toolType);
+        return false;
+    }
+    uint16_t tool = static_cast<uint16_t>(toolTypeIterator->second);
+    if (uinputAction == UinputAction::PRESS && !handleStylusDown(tool, eventTime)) {
+        return false;
+    }
+    if (!mIsStylusDown) {
+        ALOGE("Action UP or MOVE received with no prior action DOWN for stylus %d.", mFd.get());
+        return false;
+    }
+    if (uinputAction == UinputAction::RELEASE && !handleStylusUp(tool, eventTime)) {
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_X, locationX, eventTime)) {
+        ALOGE("Unsupported x-axis location passed for stylus: %d.", locationX);
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_Y, locationY, eventTime)) {
+        ALOGE("Unsupported y-axis location passed for stylus: %d.", locationY);
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_TILT_X, tiltX, eventTime)) {
+        ALOGE("Unsupported x-axis tilt passed for stylus: %d.", tiltX);
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_TILT_Y, tiltY, eventTime)) {
+        ALOGE("Unsupported y-axis tilt passed for stylus: %d.", tiltY);
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_PRESSURE, pressure, eventTime)) {
+        ALOGE("Unsupported pressure passed for stylus: %d.", pressure);
+        return false;
+    }
+    if (!writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime)) {
+        ALOGE("Failed to write SYN_REPORT for stylus motion event.");
+        return false;
+    }
+    return true;
+}
+
+bool VirtualStylus::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction,
+                                     std::chrono::nanoseconds eventTime) {
+    return writeEvKeyEvent(androidButtonCode, androidAction, BUTTON_CODE_MAPPING,
+                           VirtualMouse::BUTTON_ACTION_MAPPING, eventTime);
+}
+
+bool VirtualStylus::handleStylusDown(uint16_t tool, std::chrono::nanoseconds eventTime) {
+    if (mIsStylusDown) {
+        ALOGE("Repetitive action DOWN event received for a stylus that is already down.");
+        return false;
+    }
+    if (!writeInputEvent(EV_KEY, tool, static_cast<int32_t>(UinputAction::PRESS), eventTime)) {
+        ALOGE("Failed to write EV_KEY for stylus tool type: %u.", tool);
+        return false;
+    }
+    if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS), eventTime)) {
+        ALOGE("Failed to write BTN_TOUCH for stylus press.");
+        return false;
+    }
+    mIsStylusDown = true;
+    return true;
+}
+
+bool VirtualStylus::handleStylusUp(uint16_t tool, std::chrono::nanoseconds eventTime) {
+    if (!writeInputEvent(EV_KEY, tool, static_cast<int32_t>(UinputAction::RELEASE), eventTime)) {
+        ALOGE("Failed to write EV_KEY for stylus tool type: %u.", tool);
+        return false;
+    }
+    if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE),
+                         eventTime)) {
+        ALOGE("Failed to write BTN_TOUCH for stylus release.");
+        return false;
+    }
+    mIsStylusDown = false;
+    return true;
+}
+
 } // namespace android
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 8f6f95b..90ed2b7 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -49,12 +49,105 @@
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
     /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event directly passed through
+     * the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    const int MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1;
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event did not directly pass
+     * through the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     *
+     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
+     * obstructed in areas other than the touched location.
+     */
+    const int MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
+
+    /**
+     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+     * @hide
+     */
+    const int MOTION_EVENT_FLAG_HOVER_EXIT_PENDING = 0x4;
+
+    /**
+     * This flag indicates that the event has been generated by a gesture generator. It
+     * provides a hint to the GestureDetector to not apply any touch slop.
+     *
+     * @hide
+     */
+    const int MOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8;
+
+    /**
+     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
+     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
+     * is set, the typical actions that occur in response for a pointer going up (such as click
+     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
+     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
+     * screen.
+     *
+     * @see #ACTION_POINTER_UP
+     * @see #ACTION_CANCEL
+     */
+    const int INPUT_EVENT_FLAG_CANCELED = 0x20;
+
+    /**
+     * This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
+     * gestures to allow the user to direct gestures to an unfocused window without bringing the
+     * window into focus.
+     * @hide
+     */
+    const int MOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40;
+
+    /**
      * The input event was generated or modified by accessibility service.
      * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
      * set of flags, including in input/Input.h and in android/input.h.
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
 
+    /**
+     * Private flag that indicates when the system has detected that this motion event
+     * may be inconsistent with respect to the sequence of previously delivered motion events,
+     * such as when a pointer move event is sent but the pointer is not down.
+     *
+     * @hide
+     * @see #isTainted
+     * @see #setTainted
+     */
+    const int INPUT_EVENT_FLAG_TAINTED = 0x80000000;
+
+    /**
+     * Private flag indicating that this event was synthesized by the system and should be delivered
+     * to the accessibility focused view first. When being dispatched such an event is not handled
+     * by predecessors of the accessibility focused view and after the event reaches that view the
+     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
+     * click on any view that has accessibility focus which is semantically equivalent to asking the
+     * view to perform a click accessibility action but more generic as views not implementing click
+     * action correctly can still be activated.
+     *
+     * @hide
+     * @see #isTargetAccessibilityFocus()
+     * @see #setTargetAccessibilityFocus(boolean)
+     */
+    const int MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
+
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 
diff --git a/libs/input/android/os/IInputFlinger.aidl b/libs/input/android/os/IInputFlinger.aidl
index 00ebd4d..c1aacfb 100644
--- a/libs/input/android/os/IInputFlinger.aidl
+++ b/libs/input/android/os/IInputFlinger.aidl
@@ -16,14 +16,13 @@
 
 package android.os;
 
-import android.InputChannel;
+import android.os.InputChannelCore;
 import android.gui.FocusRequest;
-import android.gui.WindowInfo;
 
 /** @hide */
 interface IInputFlinger
 {
-    InputChannel createInputChannel(in @utf8InCpp String name);
+    InputChannelCore createInputChannel(in @utf8InCpp String name);
     void removeInputChannel(in IBinder connectionToken);
     /**
      * Sets focus to the window identified by the token. This must be called
diff --git a/libs/input/android/InputChannel.aidl b/libs/input/android/os/InputChannelCore.aidl
similarity index 72%
rename from libs/input/android/InputChannel.aidl
rename to libs/input/android/os/InputChannelCore.aidl
index c2d1112..888a553 100644
--- a/libs/input/android/InputChannel.aidl
+++ b/libs/input/android/os/InputChannelCore.aidl
@@ -15,6 +15,16 @@
 ** limitations under the License.
 */
 
-package android;
+package android.os;
 
-parcelable InputChannel cpp_header "input/InputTransport.h";
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Input channel struct for sending InputChannel between processes.
+ * @hide
+ */
+parcelable InputChannelCore {
+    @utf8InCpp String name;
+    ParcelFileDescriptor fd;
+    IBinder token;
+}
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
index 4e644ff..da62e03 100644
--- a/libs/input/android/os/InputConfig.aidl
+++ b/libs/input/android/os/InputConfig.aidl
@@ -150,4 +150,21 @@
      * likely a duplicate window with the same client token, but different bounds.
      */
     CLONE                        = 1 << 16,
+
+    /**
+     * If the stylus is currently down *anywhere* on the screen, new touches will not be delivered
+     * to the window with this flag. This helps prevent unexpected clicks on some system windows,
+     * like StatusBar and TaskBar.
+     */
+    GLOBAL_STYLUS_BLOCKS_TOUCH   = 1 << 17,
+
+    /**
+     * InputConfig used to indicate that this window is privacy sensitive. This may be used to
+     * redact input interactions from tracing or screen mirroring.
+     *
+     * This must be set on windows that use {@link WindowManager.LayoutParams#FLAG_SECURE},
+     * but it may also be set without setting FLAG_SECURE. The tracing configuration will
+     * determine how these sensitive events are eventually traced.
+     */
+     SENSITIVE_FOR_PRIVACY       = 1 << 18,
 }
diff --git a/libs/input/android/os/PointerIconType.aidl b/libs/input/android/os/PointerIconType.aidl
new file mode 100644
index 0000000..f244c62
--- /dev/null
+++ b/libs/input/android/os/PointerIconType.aidl
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2024, 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 android.os;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * Please look at frameworks/base/core/java/android/view/PointerIcon.java for the detailed
+ * explanation of each constant.
+ * @hide
+ */
+@Backing(type="int")
+enum PointerIconType {
+    CUSTOM                  = -1,
+    TYPE_NULL               = 0,
+    NOT_SPECIFIED           = 1,
+    ARROW                   = 1000,
+    CONTEXT_MENU            = 1001,
+    HAND                    = 1002,
+    HELP                    = 1003,
+    WAIT                    = 1004,
+    CELL                    = 1006,
+    CROSSHAIR               = 1007,
+    TEXT                    = 1008,
+    VERTICAL_TEXT           = 1009,
+    ALIAS                   = 1010,
+    COPY                    = 1011,
+    NO_DROP                 = 1012,
+    ALL_SCROLL              = 1013,
+    HORIZONTAL_DOUBLE_ARROW = 1014,
+    VERTICAL_DOUBLE_ARROW   = 1015,
+    TOP_RIGHT_DOUBLE_ARROW  = 1016,
+    TOP_LEFT_DOUBLE_ARROW   = 1017,
+    ZOOM_IN                 = 1018,
+    ZOOM_OUT                = 1019,
+    GRAB                    = 1020,
+    GRABBING                = 1021,
+    HANDWRITING             = 1022,
+
+    SPOT_HOVER              = 2000,
+    SPOT_TOUCH              = 2001,
+    SPOT_ANCHOR             = 2002,
+}
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index dbc002c..560166c 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -16,17 +16,17 @@
 }
 
 flag {
-  name: "enable_pointer_choreographer"
-  namespace: "input"
-  description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons"
-  bug: "293587049"
-}
-
-flag {
   name: "enable_gestures_library_timer_provider"
   namespace: "input"
   description: "Set to true to enable timer support for the touchpad Gestures library"
   bug: "297192727"
+ }
+
+ flag {
+  name: "enable_input_event_tracing"
+  namespace: "input"
+  description: "Set to true to enable input event tracing, including always-on tracing on non-user builds"
+  bug: "210460522"
 }
 
 flag {
@@ -53,15 +53,15 @@
 flag {
   name: "enable_touchpad_typing_palm_rejection"
   namespace: "input"
-  description: "Enable additional palm rejection on touchpad while typing"
+  description: "Enabling additional touchpad palm rejection will disable the tap to click while the user is typing on a physical keyboard"
   bug: "301055381"
 }
 
 flag {
-  name: "remove_app_switch_drops"
+  name: "enable_v2_touchpad_typing_palm_rejection"
   namespace: "input"
-  description: "Remove the logic of dropping events due to pending app switch"
-  bug: "284808102"
+  description: "In addition to touchpad palm rejection v1, v2 will also cancel ongoing move gestures while typing and add delay in re-enabling the tap to click."
+  bug: "301055381"
 }
 
 flag {
@@ -80,6 +80,7 @@
 
 flag {
   name: "override_key_behavior_permission_apis"
+  is_exported: true
   namespace: "input"
   description: "enable override key behavior permission APIs"
   bug: "309018874"
@@ -91,3 +92,61 @@
   description: "Remove pointer event tracking in WM after the Pointer Icon Refactor"
   bug: "315321016"
 }
+
+flag {
+  name: "enable_new_mouse_pointer_ballistics"
+  namespace: "input"
+  description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones"
+  bug: "315313622"
+}
+
+flag {
+  name: "rate_limit_user_activity_poke_in_dispatcher"
+  namespace: "input"
+  description: "Move user-activity poke rate-limiting from PowerManagerService to InputDispatcher."
+  bug: "320499729"
+}
+
+flag {
+  name: "input_device_view_behavior_api"
+  is_exported: true
+  namespace: "input"
+  description: "Controls the API to provide InputDevice view behavior."
+  bug: "246946631"
+}
+
+flag {
+  name: "enable_touchpad_fling_stop"
+  namespace: "input"
+  description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again"
+  bug: "281106755"
+}
+
+flag {
+  name: "enable_prediction_pruning_via_jerk_thresholding"
+  namespace: "input"
+  description: "Enable prediction pruning based on jerk thresholds."
+  bug: "266747654"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "device_associations"
+  namespace: "input"
+  description: "Binds InputDevice name and InputDevice description against display unique id."
+  bug: "324075859"
+}
+
+flag {
+  name: "enable_multi_device_same_window_stream"
+  namespace: "input"
+  description: "Allow multiple input devices to be active in the same window simultaneously"
+  bug: "330752824"
+}
+
+flag {
+  name: "hide_pointer_indicators_for_secure_windows"
+  namespace: "input"
+  description: "Hide touch and pointer indicators if a secure window is present on display"
+  bug: "325252005"
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 705c959..d0dbd6f 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -17,6 +17,15 @@
 //! Common definitions of the Android Input Framework in rust.
 
 use bitflags::bitflags;
+use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_CANCELED;
+use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 use std::fmt;
 
 /// The InputDevice ID.
@@ -182,18 +191,24 @@
     /// MotionEvent flags.
     #[derive(Debug)]
     pub struct MotionFlags: u32 {
-        /// FLAG_CANCELED
-        const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED as u32;
         /// FLAG_WINDOW_IS_OBSCURED
-        const WINDOW_IS_OBSCURED = input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+        const WINDOW_IS_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32;
         /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-        const WINDOW_IS_PARTIALLY_OBSCURED =
-                input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
-        /// FLAG_IS_ACCESSIBILITY_EVENT
-        const IS_ACCESSIBILITY_EVENT =
-                input_bindgen::AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+        const WINDOW_IS_PARTIALLY_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32;
+        /// FLAG_HOVER_EXIT_PENDING
+        const HOVER_EXIT_PENDING = MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32;
+        /// FLAG_IS_GENERATED_GESTURE
+        const IS_GENERATED_GESTURE = MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32;
+        /// FLAG_CANCELED
+        const CANCELED = INPUT_EVENT_FLAG_CANCELED as u32;
         /// FLAG_NO_FOCUS_CHANGE
-        const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+        const NO_FOCUS_CHANGE = MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32;
+        /// FLAG_IS_ACCESSIBILITY_EVENT
+        const IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32;
+        /// FLAG_TAINTED
+        const TAINTED = INPUT_EVENT_FLAG_TAINTED as u32;
+        /// FLAG_TARGET_ACCESSIBILITY_FOCUS
+        const TARGET_ACCESSIBILITY_FOCUS = MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32;
     }
 }
 
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 01d9599..fb3f520 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -79,12 +79,20 @@
     pointer_properties: &[RustPointerProperties],
     flags: u32,
 ) -> String {
+    let motion_flags = MotionFlags::from_bits(flags);
+    if motion_flags.is_none() {
+        panic!(
+            "The conversion of flags 0x{:08x} failed, please check if some flags have not been \
+            added to MotionFlags.",
+            flags
+        );
+    }
     let result = verifier.process_movement(
         DeviceId(device_id),
         Source::from_bits(source).unwrap(),
         action,
         pointer_properties,
-        MotionFlags::from_bits(flags).unwrap(),
+        motion_flags.unwrap(),
     );
     match result {
         Ok(()) => "".to_string(),
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 9137a34..e9d799e 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -19,6 +19,7 @@
         "InputDevice_test.cpp",
         "InputEvent_test.cpp",
         "InputPublisherAndConsumer_test.cpp",
+        "InputPublisherAndConsumerNoResampling_test.cpp",
         "InputVerifier_test.cpp",
         "MotionPredictor_test.cpp",
         "MotionPredictorMetricsManager_test.cpp",
@@ -26,6 +27,7 @@
         "TfLiteMotionPredictor_test.cpp",
         "TouchResampling_test.cpp",
         "TouchVideoFrame_test.cpp",
+        "VelocityControl_test.cpp",
         "VelocityTracker_test.cpp",
         "VerifiedInputEvent_test.cpp",
     ],
@@ -34,6 +36,7 @@
         "tensorflow_headers",
     ],
     static_libs: [
+        "libflagtest",
         "libgmock",
         "libgui_window_info_static",
         "libinput",
@@ -62,6 +65,7 @@
         "libcutils",
         "liblog",
         "libPlatformProperties",
+        "libstatslog",
         "libtinyxml2",
         "libutils",
         "server_configurable_flags",
@@ -75,19 +79,17 @@
     },
     test_suites: ["device-tests"],
     target: {
+        android: {
+            static_libs: [
+                "libstatslog_libinput",
+                "libstatssocket_lazy",
+            ],
+        },
         host: {
             sanitize: {
                 address: true,
             },
         },
-        android: {
-            static_libs: [
-                // Stats logging library and its dependencies.
-                "libstatslog_libinput",
-                "libstatsbootstrap",
-                "android.os.statsbootstrap_aidl-cpp",
-            ],
-        },
     },
 }
 
diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp
index 0661261..02d4c07 100644
--- a/libs/input/tests/InputChannel_test.cpp
+++ b/libs/input/tests/InputChannel_test.cpp
@@ -16,8 +16,6 @@
 
 #include <array>
 
-#include "TestHelpers.h"
-
 #include <unistd.h>
 #include <time.h>
 #include <errno.h>
@@ -32,37 +30,31 @@
 
 namespace android {
 
+namespace {
+bool operator==(const InputChannel& left, const InputChannel& right) {
+    struct stat lhs, rhs;
+    if (fstat(left.getFd(), &lhs) != 0) {
+        return false;
+    }
+    if (fstat(right.getFd(), &rhs) != 0) {
+        return false;
+    }
+    // If file descriptors are pointing to same inode they are duplicated fds.
+    return left.getName() == right.getName() &&
+            left.getConnectionToken() == right.getConnectionToken() && lhs.st_ino == rhs.st_ino;
+}
+} // namespace
+
 class InputChannelTest : public testing::Test {
 };
 
+TEST_F(InputChannelTest, ClientAndServerTokensMatch) {
+    std::unique_ptr<InputChannel> serverChannel, clientChannel;
 
-TEST_F(InputChannelTest, ConstructorAndDestructor_TakesOwnershipOfFileDescriptors) {
-    // Our purpose here is to verify that the input channel destructor closes the
-    // file descriptor provided to it.  One easy way is to provide it with one end
-    // of a pipe and to check for EPIPE on the other end after the channel is destroyed.
-    Pipe pipe;
-
-    android::base::unique_fd sendFd(pipe.sendFd);
-
-    std::unique_ptr<InputChannel> inputChannel =
-            InputChannel::create("channel name", std::move(sendFd), new BBinder());
-
-    EXPECT_NE(inputChannel, nullptr) << "channel should be successfully created";
-    EXPECT_STREQ("channel name", inputChannel->getName().c_str())
-            << "channel should have provided name";
-    EXPECT_NE(-1, inputChannel->getFd()) << "channel should have valid fd";
-
-    // InputChannel should be the owner of the file descriptor now
-    ASSERT_FALSE(sendFd.ok());
-}
-
-TEST_F(InputChannelTest, SetAndGetToken) {
-    Pipe pipe;
-    sp<IBinder> token = new BBinder();
-    std::unique_ptr<InputChannel> channel =
-            InputChannel::create("test channel", android::base::unique_fd(pipe.sendFd), token);
-
-    EXPECT_EQ(token, channel->getConnectionToken());
+    status_t result =
+            InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel);
+    ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
+    EXPECT_EQ(serverChannel->getConnectionToken(), clientChannel->getConnectionToken());
 }
 
 TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) {
@@ -71,8 +63,7 @@
     status_t result = InputChannel::openInputChannelPair("channel name",
             serverChannel, clientChannel);
 
-    ASSERT_EQ(OK, result)
-            << "should have successfully opened a channel pair";
+    ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
 
     // Name
     EXPECT_STREQ("channel name (server)", serverChannel->getName().c_str())
@@ -81,8 +72,7 @@
             << "client channel should have suffixed name";
 
     // Server->Client communication
-    InputMessage serverMsg;
-    memset(&serverMsg, 0, sizeof(InputMessage));
+    InputMessage serverMsg = {};
     serverMsg.header.type = InputMessage::Type::KEY;
     serverMsg.body.key.action = AKEY_EVENT_ACTION_DOWN;
     EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg))
@@ -97,8 +87,7 @@
             << "client channel should receive the correct message from server channel";
 
     // Client->Server communication
-    InputMessage clientReply;
-    memset(&clientReply, 0, sizeof(InputMessage));
+    InputMessage clientReply = {};
     clientReply.header.type = InputMessage::Type::FINISHED;
     clientReply.header.seq = 0x11223344;
     clientReply.body.finished.handled = true;
@@ -116,6 +105,48 @@
             << "server channel should receive the correct message from client channel";
 }
 
+TEST_F(InputChannelTest, ProbablyHasInput) {
+    std::unique_ptr<InputChannel> senderChannel, receiverChannel;
+
+    // Open a pair of channels.
+    status_t result =
+            InputChannel::openInputChannelPair("channel name", senderChannel, receiverChannel);
+    ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
+
+    ASSERT_FALSE(receiverChannel->probablyHasInput());
+
+    // Send one message.
+    InputMessage serverMsg = {};
+    serverMsg.header.type = InputMessage::Type::KEY;
+    serverMsg.body.key.action = AKEY_EVENT_ACTION_DOWN;
+    EXPECT_EQ(OK, senderChannel->sendMessage(&serverMsg))
+            << "server channel should be able to send message to client channel";
+
+    // Verify input is available.
+    bool hasInput = false;
+    do {
+        // The probablyHasInput() can return false positive under rare circumstances uncontrollable
+        // by the tests. Re-request the availability in this case. Returning |false| for a long
+        // time is not intended, and would cause a test timeout.
+        hasInput = receiverChannel->probablyHasInput();
+    } while (!hasInput);
+    EXPECT_TRUE(hasInput)
+            << "client channel should observe that message is available before receiving it";
+
+    // Receive (consume) the message.
+    InputMessage clientMsg;
+    EXPECT_EQ(OK, receiverChannel->receiveMessage(&clientMsg))
+            << "client channel should be able to receive message from server channel";
+    EXPECT_EQ(serverMsg.header.type, clientMsg.header.type)
+            << "client channel should receive the correct message from server channel";
+    EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action)
+            << "client channel should receive the correct message from server channel";
+
+    // Verify input is not available.
+    EXPECT_FALSE(receiverChannel->probablyHasInput())
+            << "client should not observe any more messages after receiving the single one";
+}
+
 TEST_F(InputChannelTest, ReceiveSignal_WhenNoSignalPresent_ReturnsAnError) {
     std::unique_ptr<InputChannel> serverChannel, clientChannel;
 
@@ -195,25 +226,6 @@
     }
 }
 
-TEST_F(InputChannelTest, InputChannelParcelAndUnparcel) {
-    std::unique_ptr<InputChannel> serverChannel, clientChannel;
-
-    status_t result =
-            InputChannel::openInputChannelPair("channel parceling", serverChannel, clientChannel);
-
-    ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
-
-    InputChannel chan;
-    Parcel parcel;
-    ASSERT_EQ(OK, serverChannel->writeToParcel(&parcel));
-    parcel.setDataPosition(0);
-    chan.readFromParcel(&parcel);
-
-    EXPECT_EQ(chan == *serverChannel, true)
-            << "inputchannel should be equal after parceling and unparceling.\n"
-            << "name " << chan.getName() << " name " << serverChannel->getName();
-}
-
 TEST_F(InputChannelTest, DuplicateChannelAndAssertEqual) {
     std::unique_ptr<InputChannel> serverChannel, clientChannel;
 
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index a965573..476b5cf 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -21,16 +21,28 @@
 #include <attestation/HmacKeyManager.h>
 #include <binder/Parcel.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/Input.h>
+#include <input/InputEventBuilders.h>
 
 namespace android {
 
 // Default display id.
-static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
+static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 
 static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
 
+static constexpr auto POINTER_0_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static constexpr auto POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static constexpr auto POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static constexpr auto POINTER_1_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
 class BaseTest : public testing::Test {
 protected:
     static constexpr std::array<uint8_t, 32> HMAC = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
@@ -216,7 +228,7 @@
     ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource());
 
     // Set display id.
-    constexpr int32_t newDisplayId = 2;
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
     event.setDisplayId(newDisplayId);
     ASSERT_EQ(newDisplayId, event.getDisplayId());
 }
@@ -358,8 +370,10 @@
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, event->getButtonState());
     ASSERT_EQ(MotionClassification::NONE, event->getClassification());
     EXPECT_EQ(mTransform, event->getTransform());
-    ASSERT_EQ(X_OFFSET, event->getXOffset());
-    ASSERT_EQ(Y_OFFSET, event->getYOffset());
+    ASSERT_NEAR((-RAW_X_OFFSET / RAW_X_SCALE) * X_SCALE + X_OFFSET, event->getRawXOffset(),
+                EPSILON);
+    ASSERT_NEAR((-RAW_Y_OFFSET / RAW_Y_SCALE) * Y_SCALE + Y_OFFSET, event->getRawYOffset(),
+                EPSILON);
     ASSERT_EQ(2.0f, event->getXPrecision());
     ASSERT_EQ(2.1f, event->getYPrecision());
     ASSERT_EQ(ARBITRARY_DOWN_TIME, event->getDownTime());
@@ -513,7 +527,7 @@
     ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource());
 
     // Set displayId.
-    constexpr int32_t newDisplayId = 2;
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
     event.setDisplayId(newDisplayId);
     ASSERT_EQ(newDisplayId, event.getDisplayId());
 
@@ -554,25 +568,168 @@
     ASSERT_EQ(event.getX(0), copy.getX(0));
 }
 
+TEST_F(MotionEventTest, SplitPointerDown) {
+    MotionEvent event = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .build();
+
+    MotionEvent splitDown;
+    std::bitset<MAX_POINTER_ID + 1> splitDownIds{};
+    splitDownIds.set(6, true);
+    splitDown.splitFrom(event, splitDownIds, /*eventId=*/42);
+    ASSERT_EQ(splitDown.getAction(), AMOTION_EVENT_ACTION_DOWN);
+    ASSERT_EQ(splitDown.getPointerCount(), 1u);
+    ASSERT_EQ(splitDown.getPointerId(0), 6);
+    ASSERT_EQ(splitDown.getX(0), 6);
+    ASSERT_EQ(splitDown.getY(0), 6);
+
+    MotionEvent splitPointerDown;
+    std::bitset<MAX_POINTER_ID + 1> splitPointerDownIds{};
+    splitPointerDownIds.set(6, true);
+    splitPointerDownIds.set(8, true);
+    splitPointerDown.splitFrom(event, splitPointerDownIds, /*eventId=*/42);
+    ASSERT_EQ(splitPointerDown.getAction(), POINTER_0_DOWN);
+    ASSERT_EQ(splitPointerDown.getPointerCount(), 2u);
+    ASSERT_EQ(splitPointerDown.getPointerId(0), 6);
+    ASSERT_EQ(splitPointerDown.getX(0), 6);
+    ASSERT_EQ(splitPointerDown.getY(0), 6);
+    ASSERT_EQ(splitPointerDown.getPointerId(1), 8);
+    ASSERT_EQ(splitPointerDown.getX(1), 8);
+    ASSERT_EQ(splitPointerDown.getY(1), 8);
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(4, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/43);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 1u);
+    ASSERT_EQ(splitMove.getPointerId(0), 4);
+    ASSERT_EQ(splitMove.getX(0), 4);
+    ASSERT_EQ(splitMove.getY(0), 4);
+}
+
+TEST_F(MotionEventTest, SplitPointerUp) {
+    MotionEvent event = MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .build();
+
+    MotionEvent splitUp;
+    std::bitset<MAX_POINTER_ID + 1> splitUpIds{};
+    splitUpIds.set(4, true);
+    splitUp.splitFrom(event, splitUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitUp.getAction(), AMOTION_EVENT_ACTION_UP);
+    ASSERT_EQ(splitUp.getPointerCount(), 1u);
+    ASSERT_EQ(splitUp.getPointerId(0), 4);
+    ASSERT_EQ(splitUp.getX(0), 4);
+    ASSERT_EQ(splitUp.getY(0), 4);
+
+    MotionEvent splitPointerUp;
+    std::bitset<MAX_POINTER_ID + 1> splitPointerUpIds{};
+    splitPointerUpIds.set(4, true);
+    splitPointerUpIds.set(8, true);
+    splitPointerUp.splitFrom(event, splitPointerUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitPointerUp.getAction(), POINTER_0_UP);
+    ASSERT_EQ(splitPointerUp.getPointerCount(), 2u);
+    ASSERT_EQ(splitPointerUp.getPointerId(0), 4);
+    ASSERT_EQ(splitPointerUp.getX(0), 4);
+    ASSERT_EQ(splitPointerUp.getY(0), 4);
+    ASSERT_EQ(splitPointerUp.getPointerId(1), 8);
+    ASSERT_EQ(splitPointerUp.getX(1), 8);
+    ASSERT_EQ(splitPointerUp.getY(1), 8);
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(6, true);
+    splitMoveIds.set(8, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/43);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 2u);
+    ASSERT_EQ(splitMove.getPointerId(0), 6);
+    ASSERT_EQ(splitMove.getX(0), 6);
+    ASSERT_EQ(splitMove.getY(0), 6);
+    ASSERT_EQ(splitMove.getPointerId(1), 8);
+    ASSERT_EQ(splitMove.getX(1), 8);
+    ASSERT_EQ(splitMove.getY(1), 8);
+}
+
+TEST_F(MotionEventTest, SplitPointerUpCancel) {
+    MotionEvent event = MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .addFlag(AMOTION_EVENT_FLAG_CANCELED)
+                                .build();
+
+    MotionEvent splitUp;
+    std::bitset<MAX_POINTER_ID + 1> splitUpIds{};
+    splitUpIds.set(6, true);
+    splitUp.splitFrom(event, splitUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitUp.getAction(), AMOTION_EVENT_ACTION_CANCEL);
+    ASSERT_EQ(splitUp.getPointerCount(), 1u);
+    ASSERT_EQ(splitUp.getPointerId(0), 6);
+    ASSERT_EQ(splitUp.getX(0), 6);
+    ASSERT_EQ(splitUp.getY(0), 6);
+}
+
+TEST_F(MotionEventTest, SplitPointerMove) {
+    MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                                .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                                .build();
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(4, true);
+    splitMoveIds.set(8, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/42);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 2u);
+    ASSERT_EQ(splitMove.getPointerId(0), 4);
+    ASSERT_EQ(splitMove.getX(0), event.getX(0));
+    ASSERT_EQ(splitMove.getY(0), event.getY(0));
+    ASSERT_EQ(splitMove.getRawX(0), event.getRawX(0));
+    ASSERT_EQ(splitMove.getRawY(0), event.getRawY(0));
+    ASSERT_EQ(splitMove.getPointerId(1), 8);
+    ASSERT_EQ(splitMove.getX(1), event.getX(2));
+    ASSERT_EQ(splitMove.getY(1), event.getY(2));
+    ASSERT_EQ(splitMove.getRawX(1), event.getRawX(2));
+    ASSERT_EQ(splitMove.getRawY(1), event.getRawY(2));
+}
+
 TEST_F(MotionEventTest, OffsetLocation) {
     MotionEvent event;
     initializeEventWithHistory(&event);
+    const float xOffset = event.getRawXOffset();
+    const float yOffset = event.getRawYOffset();
 
     event.offsetLocation(5.0f, -2.0f);
 
-    ASSERT_EQ(X_OFFSET + 5.0f, event.getXOffset());
-    ASSERT_EQ(Y_OFFSET - 2.0f, event.getYOffset());
+    ASSERT_EQ(xOffset + 5.0f, event.getRawXOffset());
+    ASSERT_EQ(yOffset - 2.0f, event.getRawYOffset());
 }
 
 TEST_F(MotionEventTest, Scale) {
     MotionEvent event;
     initializeEventWithHistory(&event);
     const float unscaledOrientation = event.getOrientation(0);
+    const float unscaledXOffset = event.getRawXOffset();
+    const float unscaledYOffset = event.getRawYOffset();
 
     event.scale(2.0f);
 
-    ASSERT_EQ(X_OFFSET * 2, event.getXOffset());
-    ASSERT_EQ(Y_OFFSET * 2, event.getYOffset());
+    ASSERT_EQ(unscaledXOffset * 2, event.getRawXOffset());
+    ASSERT_EQ(unscaledYOffset * 2, event.getRawYOffset());
 
     ASSERT_NEAR((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0), EPSILON);
     ASSERT_NEAR((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0), EPSILON);
@@ -701,11 +858,10 @@
     pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy);
     nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
     MotionEvent event;
-    event.initialize(InputEvent::nextId(), /* deviceId */ 1, source,
-                     /* displayId */ 0, INVALID_HMAC, action,
-                     /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
-                     /* buttonState */ 0, MotionClassification::NONE, transform,
-                     /* xPrecision */ 0, /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+    event.initialize(InputEvent::nextId(), /*deviceId=*/1, source, ui::LogicalDisplayId::DEFAULT,
+                     INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0,
+                     AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, transform,
+                     /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, rawTransform, eventTime, eventTime,
                      pointerCoords.size(), pointerProperties.data(), pointerCoords.data());
     return event;
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
new file mode 100644
index 0000000..70529bb
--- /dev/null
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -0,0 +1,815 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <attestation/HmacKeyManager.h>
+#include <ftl/enum.h>
+#include <gtest/gtest.h>
+#include <input/BlockingQueue.h>
+#include <input/InputConsumerNoResampling.h>
+#include <input/InputTransport.h>
+
+using android::base::Result;
+
+namespace android {
+
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+static constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static auto constexpr TIMEOUT = 5s;
+
+struct Pointer {
+    int32_t id;
+    float x;
+    float y;
+    bool isResampled = false;
+};
+
+// A collection of arguments to be sent as publishMotionEvent(). The saved members of this struct
+// allow to check the expectations against the event acquired from the InputConsumerCallbacks. To
+// help simplify expectation checking it carries members not present in MotionEvent, like
+// |rawXScale|.
+struct PublishMotionArgs {
+    const int32_t action;
+    const nsecs_t downTime;
+    const uint32_t seq;
+    const int32_t eventId;
+    const int32_t deviceId = 1;
+    const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
+    const ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
+    const int32_t actionButton = 0;
+    const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
+    const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+    const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
+    const MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE;
+    const float xScale = 2;
+    const float yScale = 3;
+    const float xOffset = -10;
+    const float yOffset = -20;
+    const float rawXScale = 4;
+    const float rawYScale = -5;
+    const float rawXOffset = -11;
+    const float rawYOffset = 42;
+    const float xPrecision = 0.25;
+    const float yPrecision = 0.5;
+    const float xCursorPosition = 1.3;
+    const float yCursorPosition = 50.6;
+    std::array<uint8_t, 32> hmac;
+    int32_t flags;
+    ui::Transform transform;
+    ui::Transform rawTransform;
+    const nsecs_t eventTime;
+    size_t pointerCount;
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+
+    PublishMotionArgs(int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers,
+                      const uint32_t seq);
+};
+
+PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime,
+                                     const std::vector<Pointer>& pointers, const uint32_t inSeq)
+      : action(inAction),
+        downTime(inDownTime),
+        seq(inSeq),
+        eventId(InputEvent::nextId()),
+        eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) {
+    hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
+            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
+
+    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    pointerCount = pointers.size();
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back({});
+        pointerProperties[i].clear();
+        pointerProperties[i].id = pointers[i].id;
+        pointerProperties[i].toolType = ToolType::FINGER;
+
+        pointerCoords.push_back({});
+        pointerCoords[i].clear();
+        pointerCoords[i].isResampled = pointers[i].isResampled;
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
+    }
+    transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
+    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+}
+
+// Checks expectations against |motionEvent| acquired from an InputConsumer. Floating point
+// comparisons limit precision to EPSILON.
+void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& motionEvent) {
+    EXPECT_EQ(args.eventId, motionEvent.getId());
+    EXPECT_EQ(args.deviceId, motionEvent.getDeviceId());
+    EXPECT_EQ(args.source, motionEvent.getSource());
+    EXPECT_EQ(args.displayId, motionEvent.getDisplayId());
+    EXPECT_EQ(args.hmac, motionEvent.getHmac());
+    EXPECT_EQ(args.action, motionEvent.getAction());
+    EXPECT_EQ(args.downTime, motionEvent.getDownTime());
+    EXPECT_EQ(args.flags, motionEvent.getFlags());
+    EXPECT_EQ(args.edgeFlags, motionEvent.getEdgeFlags());
+    EXPECT_EQ(args.metaState, motionEvent.getMetaState());
+    EXPECT_EQ(args.buttonState, motionEvent.getButtonState());
+    EXPECT_EQ(args.classification, motionEvent.getClassification());
+    EXPECT_EQ(args.transform, motionEvent.getTransform());
+    EXPECT_NEAR((-args.rawXOffset / args.rawXScale) * args.xScale + args.xOffset,
+                motionEvent.getRawXOffset(), EPSILON);
+    EXPECT_NEAR((-args.rawYOffset / args.rawYScale) * args.yScale + args.yOffset,
+                motionEvent.getRawYOffset(), EPSILON);
+    EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision());
+    EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision());
+    EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.yCursorPosition, motionEvent.getRawYCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.xCursorPosition * args.xScale + args.xOffset, motionEvent.getXCursorPosition(),
+                EPSILON);
+    EXPECT_NEAR(args.yCursorPosition * args.yScale + args.yOffset, motionEvent.getYCursorPosition(),
+                EPSILON);
+    EXPECT_EQ(args.rawTransform, motionEvent.getRawTransform());
+    EXPECT_EQ(args.eventTime, motionEvent.getEventTime());
+    EXPECT_EQ(args.pointerCount, motionEvent.getPointerCount());
+    EXPECT_EQ(0U, motionEvent.getHistorySize());
+
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(args.pointerProperties[i].id, motionEvent.getPointerId(i));
+        EXPECT_EQ(args.pointerProperties[i].toolType, motionEvent.getToolType(i));
+
+        const auto& pc = args.pointerCoords[i];
+        EXPECT_EQ(pc, motionEvent.getSamplePointerCoords()[i]);
+
+        EXPECT_NEAR(pc.getX() * args.rawXScale + args.rawXOffset, motionEvent.getRawX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.rawYScale + args.rawYOffset, motionEvent.getRawY(i), EPSILON);
+        EXPECT_NEAR(pc.getX() * args.xScale + args.xOffset, motionEvent.getX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.yScale + args.yOffset, motionEvent.getY(i), EPSILON);
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent.getPressure(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent.getSize(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent.getTouchMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent.getTouchMinor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent.getToolMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent.getToolMinor(i));
+
+        // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
+        // "up", and the positive y direction is "down".
+        const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+        const float x = sinf(unscaledOrientation) * args.xScale;
+        const float y = -cosf(unscaledOrientation) * args.yScale;
+        EXPECT_EQ(atan2f(x, -y), motionEvent.getOrientation(i));
+    }
+}
+
+void publishMotionEvent(InputPublisher& publisher, const PublishMotionArgs& a) {
+    status_t status =
+            publisher.publishMotionEvent(a.seq, a.eventId, a.deviceId, a.source, a.displayId,
+                                         a.hmac, a.action, a.actionButton, a.flags, a.edgeFlags,
+                                         a.metaState, a.buttonState, a.classification, a.transform,
+                                         a.xPrecision, a.yPrecision, a.xCursorPosition,
+                                         a.yCursorPosition, a.rawTransform, a.downTime, a.eventTime,
+                                         a.pointerCount, a.pointerProperties.data(),
+                                         a.pointerCoords.data());
+    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
+}
+
+Result<InputPublisher::ConsumerResponse> receiveConsumerResponse(
+        InputPublisher& publisher, std::chrono::milliseconds timeout) {
+    const std::chrono::time_point start = std::chrono::steady_clock::now();
+
+    while (true) {
+        Result<InputPublisher::ConsumerResponse> result = publisher.receiveConsumerResponse();
+        if (result.ok()) {
+            return result;
+        }
+        const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
+        if (waited > timeout) {
+            return result;
+        }
+    }
+}
+
+void verifyFinishedSignal(InputPublisher& publisher, uint32_t seq, nsecs_t publishTime) {
+    Result<InputPublisher::ConsumerResponse> result = receiveConsumerResponse(publisher, TIMEOUT);
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse returned " << result.error().message();
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
+            << "finished signal's consume time should be greater than publish time";
+}
+
+} // namespace
+
+class InputConsumerMessageHandler : public MessageHandler {
+public:
+    InputConsumerMessageHandler(std::function<void(const Message&)> function)
+          : mFunction(function) {}
+
+private:
+    void handleMessage(const Message& message) override { mFunction(message); }
+
+    std::function<void(const Message&)> mFunction;
+};
+
+class InputPublisherAndConsumerNoResamplingTest : public testing::Test,
+                                                  public InputConsumerCallbacks {
+protected:
+    std::unique_ptr<InputChannel> mClientChannel;
+    std::unique_ptr<InputPublisher> mPublisher;
+    std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+    std::thread mLooperThread;
+    sp<Looper> mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+
+    // LOOPER CONTROL
+    // Set to false when you want the looper to exit
+    std::atomic<bool> mExitLooper = false;
+    std::mutex mLock;
+
+    // Used by test to notify looper that the value of "mLooperMayProceed" has changed
+    std::condition_variable mNotifyLooperMayProceed;
+    bool mLooperMayProceed GUARDED_BY(mLock){true};
+    // Used by looper to notify the test that it's about to block on "mLooperMayProceed" -> true
+    std::condition_variable mNotifyLooperWaiting;
+    bool mLooperIsBlocked GUARDED_BY(mLock){false};
+
+    std::condition_variable mNotifyConsumerDestroyed;
+    bool mConsumerDestroyed GUARDED_BY(mLock){false};
+
+    void runLooper() {
+        static constexpr int LOOP_INDEFINITELY = -1;
+        Looper::setForThread(mLooper);
+        // Loop forever -- this thread is dedicated to servicing the looper callbacks.
+        while (!mExitLooper) {
+            mLooper->pollOnce(/*timeoutMillis=*/LOOP_INDEFINITELY);
+        }
+    }
+
+    void SetUp() override {
+        std::unique_ptr<InputChannel> serverChannel;
+        status_t result =
+                InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel);
+        ASSERT_EQ(OK, result);
+
+        mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
+        mMessageHandler = sp<InputConsumerMessageHandler>::make(
+                [this](const Message& message) { handleMessage(message); });
+        mLooperThread = std::thread([this] { runLooper(); });
+        sendMessage(LooperMessage::CREATE_CONSUMER);
+    }
+
+    void publishAndConsumeKeyEvent();
+    void publishAndConsumeMotionStream();
+    void publishAndConsumeMotionDown(nsecs_t downTime);
+    void publishAndConsumeBatchedMotionMove(nsecs_t downTime);
+    void publishAndConsumeFocusEvent();
+    void publishAndConsumeCaptureEvent();
+    void publishAndConsumeDragEvent();
+    void publishAndConsumeTouchModeEvent();
+    void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+                                      const std::vector<Pointer>& pointers);
+    void TearDown() override {
+        // Destroy the consumer, flushing any of the pending ack's.
+        sendMessage(LooperMessage::DESTROY_CONSUMER);
+        {
+            std::unique_lock lock(mLock);
+            base::ScopedLockAssertion assumeLocked(mLock);
+            mNotifyConsumerDestroyed.wait(lock, [this] { return mConsumerDestroyed; });
+        }
+        // Stop the looper thread so that we can destroy the object.
+        mExitLooper = true;
+        mLooper->wake();
+        mLooperThread.join();
+    }
+
+protected:
+    // Interaction with the looper thread
+    enum class LooperMessage : int {
+        CALL_PROBABLY_HAS_INPUT,
+        CREATE_CONSUMER,
+        DESTROY_CONSUMER,
+        CALL_REPORT_TIMELINE,
+        BLOCK_LOOPER,
+    };
+    void sendMessage(LooperMessage message);
+    struct ReportTimelineArgs {
+        int32_t inputEventId;
+        nsecs_t gpuCompletedTime;
+        nsecs_t presentTime;
+    };
+    // The input to the function "InputConsumer::reportTimeline". Populated on the test thread and
+    // accessed on the looper thread.
+    BlockingQueue<ReportTimelineArgs> mReportTimelineArgs;
+    // The output of calling "InputConsumer::probablyHasInput()". Populated on the looper thread and
+    // accessed on the test thread.
+    BlockingQueue<bool> mProbablyHasInputResponses;
+
+private:
+    sp<MessageHandler> mMessageHandler;
+    void handleMessage(const Message& message);
+
+    static auto constexpr NO_EVENT_TIMEOUT = 10ms;
+    // The sequence number to use when publishing the next event
+    uint32_t mSeq = 1;
+
+    BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
+    BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents;
+    BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents;
+    BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+    BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents;
+    BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+    // InputConsumerCallbacks interface
+    void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
+        mKeyEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
+        mMotionEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+        if (!mConsumer->probablyHasInput()) {
+            ADD_FAILURE() << "should deterministically have input because there is a batch";
+        }
+        mConsumer->consumeBatchedInputEvents(std::nullopt);
+    };
+    void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
+        mFocusEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+    void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
+        mCaptureEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+    void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
+        mDragEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
+        mTouchModeEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+};
+
+void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) {
+    Message msg{ftl::to_underlying(message)};
+    mLooper->sendMessage(mMessageHandler, msg);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) {
+    switch (static_cast<LooperMessage>(message.what)) {
+        case LooperMessage::CALL_PROBABLY_HAS_INPUT: {
+            mProbablyHasInputResponses.push(mConsumer->probablyHasInput());
+            break;
+        }
+        case LooperMessage::CREATE_CONSUMER: {
+            mConsumer = std::make_unique<InputConsumerNoResampling>(std::move(mClientChannel),
+                                                                    mLooper, *this);
+            break;
+        }
+        case LooperMessage::DESTROY_CONSUMER: {
+            mConsumer = nullptr;
+            {
+                std::unique_lock lock(mLock);
+                mConsumerDestroyed = true;
+            }
+            mNotifyConsumerDestroyed.notify_all();
+            break;
+        }
+        case LooperMessage::CALL_REPORT_TIMELINE: {
+            std::optional<ReportTimelineArgs> args = mReportTimelineArgs.pop();
+            if (!args.has_value()) {
+                ADD_FAILURE() << "Couldn't get the 'reportTimeline' args in time";
+                return;
+            }
+            mConsumer->reportTimeline(args->inputEventId, args->gpuCompletedTime,
+                                      args->presentTime);
+            break;
+        }
+        case LooperMessage::BLOCK_LOOPER: {
+            {
+                std::unique_lock lock(mLock);
+                mLooperIsBlocked = true;
+            }
+            mNotifyLooperWaiting.notify_all();
+
+            {
+                std::unique_lock lock(mLock);
+                base::ScopedLockAssertion assumeLocked(mLock);
+                mNotifyLooperMayProceed.wait(lock, [this] { return mLooperMayProceed; });
+            }
+
+            {
+                std::unique_lock lock(mLock);
+                mLooperIsBlocked = false;
+            }
+            mNotifyLooperWaiting.notify_all();
+            break;
+        }
+    }
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeKeyEvent() {
+    status_t status;
+
+    const uint32_t seq = mSeq++;
+    int32_t eventId = InputEvent::nextId();
+    constexpr int32_t deviceId = 1;
+    constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
+    constexpr ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
+    constexpr std::array<uint8_t, 32> hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21,
+                                              20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,
+                                              9,  8,  7,  6,  5,  4,  3,  2,  1,  0};
+    constexpr int32_t action = AKEY_EVENT_ACTION_DOWN;
+    constexpr int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM;
+    constexpr int32_t keyCode = AKEYCODE_ENTER;
+    constexpr int32_t scanCode = 13;
+    constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+    constexpr int32_t repeatCount = 1;
+    constexpr nsecs_t downTime = 3;
+    constexpr nsecs_t eventTime = 4;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishKeyEvent(seq, eventId, deviceId, source, displayId, hmac, action,
+                                         flags, keyCode, scanCode, metaState, repeatCount, downTime,
+                                         eventTime);
+    ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK";
+
+    std::optional<std::unique_ptr<KeyEvent>> optKeyEvent = mKeyEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optKeyEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<KeyEvent> keyEvent = std::move(*optKeyEvent);
+
+    sendMessage(LooperMessage::CALL_PROBABLY_HAS_INPUT);
+    std::optional<bool> probablyHasInput = mProbablyHasInputResponses.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(probablyHasInput.has_value());
+    ASSERT_FALSE(probablyHasInput.value()) << "no events should be waiting after being consumed";
+
+    EXPECT_EQ(eventId, keyEvent->getId());
+    EXPECT_EQ(deviceId, keyEvent->getDeviceId());
+    EXPECT_EQ(source, keyEvent->getSource());
+    EXPECT_EQ(displayId, keyEvent->getDisplayId());
+    EXPECT_EQ(hmac, keyEvent->getHmac());
+    EXPECT_EQ(action, keyEvent->getAction());
+    EXPECT_EQ(flags, keyEvent->getFlags());
+    EXPECT_EQ(keyCode, keyEvent->getKeyCode());
+    EXPECT_EQ(scanCode, keyEvent->getScanCode());
+    EXPECT_EQ(metaState, keyEvent->getMetaState());
+    EXPECT_EQ(repeatCount, keyEvent->getRepeatCount());
+    EXPECT_EQ(downTime, keyEvent->getDownTime());
+    EXPECT_EQ(eventTime, keyEvent->getEventTime());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionStream() {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionDown(nsecs_t downTime) {
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMove(
+        nsecs_t downTime) {
+    uint32_t seq = mSeq++;
+    const std::vector<Pointer> pointers = {Pointer{.id = 0, .x = 20, .y = 30}};
+    PublishMotionArgs args(AMOTION_EVENT_ACTION_MOVE, downTime, pointers, seq);
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    // Block the looper thread, preventing it from being able to service any of the fd callbacks.
+
+    {
+        std::scoped_lock lock(mLock);
+        mLooperMayProceed = false;
+    }
+    sendMessage(LooperMessage::BLOCK_LOOPER);
+    {
+        std::unique_lock lock(mLock);
+        mNotifyLooperWaiting.wait(lock, [this] { return mLooperIsBlocked; });
+    }
+
+    publishMotionEvent(*mPublisher, args);
+
+    // Ensure no event arrives because the UI thread is blocked
+    std::optional<std::unique_ptr<MotionEvent>> noEvent =
+            mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT);
+    ASSERT_FALSE(noEvent.has_value()) << "Got unexpected event: " << *noEvent;
+
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_FALSE(result.ok());
+    ASSERT_EQ(WOULD_BLOCK, result.error().code());
+
+    // We shouldn't be calling mConsumer on the UI thread, but in this situation, the looper
+    // thread is locked, so this should be safe to do.
+    ASSERT_TRUE(mConsumer->probablyHasInput())
+            << "should deterministically have input because there is a batch";
+
+    // Now, unblock the looper thread, so that the event can arrive.
+    {
+        std::scoped_lock lock(mLock);
+        mLooperMayProceed = true;
+    }
+    mNotifyLooperMayProceed.notify_all();
+
+    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optMotion.has_value());
+    std::unique_ptr<MotionEvent> motion = std::move(*optMotion);
+    ASSERT_EQ(ACTION_MOVE, motion->getAction());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionEvent(
+        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
+    uint32_t seq = mSeq++;
+    PublishMotionArgs args(action, downTime, pointers, seq);
+    nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, args);
+
+    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optMotion.has_value());
+    std::unique_ptr<MotionEvent> event = std::move(*optMotion);
+
+    verifyArgsEqualToEvent(args, *event);
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeFocusEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool hasFocus = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishFocusEvent(seq, eventId, hasFocus);
+    ASSERT_EQ(OK, status) << "publisher publishFocusEvent should return OK";
+
+    std::optional<std::unique_ptr<FocusEvent>> optFocusEvent = mFocusEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optFocusEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<FocusEvent> focusEvent = std::move(*optFocusEvent);
+    EXPECT_EQ(eventId, focusEvent->getId());
+    EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeCaptureEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 42;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool captureEnabled = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishCaptureEvent(seq, eventId, captureEnabled);
+    ASSERT_EQ(OK, status) << "publisher publishCaptureEvent should return OK";
+
+    std::optional<std::unique_ptr<CaptureEvent>> optEvent = mCaptureEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<CaptureEvent> event = std::move(*optEvent);
+
+    const CaptureEvent& captureEvent = *event;
+    EXPECT_EQ(eventId, captureEvent.getId());
+    EXPECT_EQ(captureEnabled, captureEvent.getPointerCaptureEnabled());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeDragEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool isExiting = false;
+    constexpr float x = 10;
+    constexpr float y = 15;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishDragEvent(seq, eventId, x, y, isExiting);
+    ASSERT_EQ(OK, status) << "publisher publishDragEvent should return OK";
+
+    std::optional<std::unique_ptr<DragEvent>> optEvent = mDragEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<DragEvent> event = std::move(*optEvent);
+
+    const DragEvent& dragEvent = *event;
+    EXPECT_EQ(eventId, dragEvent.getId());
+    EXPECT_EQ(isExiting, dragEvent.isExiting());
+    EXPECT_EQ(x, dragEvent.getX());
+    EXPECT_EQ(y, dragEvent.getY());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeTouchModeEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool touchModeEnabled = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled);
+    ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK";
+
+    std::optional<std::unique_ptr<TouchModeEvent>> optEvent =
+            mTouchModeEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value());
+    std::unique_ptr<TouchModeEvent> event = std::move(*optEvent);
+
+    const TouchModeEvent& touchModeEvent = *event;
+    EXPECT_EQ(eventId, touchModeEvent.getId());
+    EXPECT_EQ(touchModeEnabled, touchModeEvent.isInTouchMode());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) {
+    const int32_t inputEventId = 20;
+    const nsecs_t gpuCompletedTime = 30;
+    const nsecs_t presentTime = 40;
+
+    mReportTimelineArgs.emplace(inputEventId, gpuCompletedTime, presentTime);
+    sendMessage(LooperMessage::CALL_REPORT_TIMELINE);
+
+    Result<InputPublisher::ConsumerResponse> result = receiveConsumerResponse(*mPublisher, TIMEOUT);
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Timeline>(*result));
+    const InputPublisher::Timeline& timeline = std::get<InputPublisher::Timeline>(*result);
+    ASSERT_EQ(inputEventId, timeline.inputEventId);
+    ASSERT_EQ(gpuCompletedTime, timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]);
+    ASSERT_EQ(presentTime, timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishKeyEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionMoveEvent_EndToEnd) {
+    // Publish a DOWN event before MOVE to pass the InputVerifier checks.
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionDown(downTime));
+
+    // Publish the MOVE event and check expectations.
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeBatchedMotionMove(downTime));
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishFocusEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishCaptureEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishDragEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishTouchModeEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = 1;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerCoords[i].clear();
+    }
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = 0;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = MAX_POINTERS + 1;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerCoords[i].clear();
+    }
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMultipleEvents_EndToEnd) {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
+}
+
+} // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 06b841b..48512f7 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-#include "TestHelpers.h"
-
 #include <attestation/HmacKeyManager.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
+#include <input/InputConsumer.h>
 #include <input/InputTransport.h>
 
 using android::base::Result;
@@ -40,11 +38,188 @@
     bool isResampled = false;
 };
 
+// A collection of arguments to be sent as publishMotionEvent(). The saved members of this struct
+// allow to check the expectations against the event acquired from the InputReceiver. To help
+// simplify expectation checking it carries members not present in MotionEvent, like |rawXScale|.
+struct PublishMotionArgs {
+    const int32_t action;
+    const nsecs_t downTime;
+    const uint32_t seq;
+    const int32_t eventId;
+    const int32_t deviceId = 1;
+    const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
+    const ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
+    const int32_t actionButton = 0;
+    const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
+    const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+    const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
+    const MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE;
+    const float xScale = 2;
+    const float yScale = 3;
+    const float xOffset = -10;
+    const float yOffset = -20;
+    const float rawXScale = 4;
+    const float rawYScale = -5;
+    const float rawXOffset = -11;
+    const float rawYOffset = 42;
+    const float xPrecision = 0.25;
+    const float yPrecision = 0.5;
+    const float xCursorPosition = 1.3;
+    const float yCursorPosition = 50.6;
+    std::array<uint8_t, 32> hmac;
+    int32_t flags;
+    ui::Transform transform;
+    ui::Transform rawTransform;
+    const nsecs_t eventTime;
+    size_t pointerCount;
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+
+    PublishMotionArgs(int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers,
+                      const uint32_t seq);
+};
+
+PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime,
+                                     const std::vector<Pointer>& pointers, const uint32_t inSeq)
+      : action(inAction),
+        downTime(inDownTime),
+        seq(inSeq),
+        eventId(InputEvent::nextId()),
+        eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) {
+    hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
+            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
+
+    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    pointerCount = pointers.size();
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back({});
+        pointerProperties[i].clear();
+        pointerProperties[i].id = pointers[i].id;
+        pointerProperties[i].toolType = ToolType::FINGER;
+
+        pointerCoords.push_back({});
+        pointerCoords[i].clear();
+        pointerCoords[i].isResampled = pointers[i].isResampled;
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
+    }
+    transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
+    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+}
+
+// Checks expectations against |motionEvent| acquired from an InputConsumer. Floating point
+// comparisons limit precision to EPSILON.
+void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& motionEvent) {
+    EXPECT_EQ(args.eventId, motionEvent.getId());
+    EXPECT_EQ(args.deviceId, motionEvent.getDeviceId());
+    EXPECT_EQ(args.source, motionEvent.getSource());
+    EXPECT_EQ(args.displayId, motionEvent.getDisplayId());
+    EXPECT_EQ(args.hmac, motionEvent.getHmac());
+    EXPECT_EQ(args.action, motionEvent.getAction());
+    EXPECT_EQ(args.downTime, motionEvent.getDownTime());
+    EXPECT_EQ(args.flags, motionEvent.getFlags());
+    EXPECT_EQ(args.edgeFlags, motionEvent.getEdgeFlags());
+    EXPECT_EQ(args.metaState, motionEvent.getMetaState());
+    EXPECT_EQ(args.buttonState, motionEvent.getButtonState());
+    EXPECT_EQ(args.classification, motionEvent.getClassification());
+    EXPECT_EQ(args.transform, motionEvent.getTransform());
+    EXPECT_NEAR((-args.rawXOffset / args.rawXScale) * args.xScale + args.xOffset,
+                motionEvent.getRawXOffset(), EPSILON);
+    EXPECT_NEAR((-args.rawYOffset / args.rawYScale) * args.yScale + args.yOffset,
+                motionEvent.getRawYOffset(), EPSILON);
+    EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision());
+    EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision());
+    EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.yCursorPosition, motionEvent.getRawYCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.xCursorPosition * args.xScale + args.xOffset, motionEvent.getXCursorPosition(),
+                EPSILON);
+    EXPECT_NEAR(args.yCursorPosition * args.yScale + args.yOffset, motionEvent.getYCursorPosition(),
+                EPSILON);
+    EXPECT_EQ(args.rawTransform, motionEvent.getRawTransform());
+    EXPECT_EQ(args.eventTime, motionEvent.getEventTime());
+    EXPECT_EQ(args.pointerCount, motionEvent.getPointerCount());
+    EXPECT_EQ(0U, motionEvent.getHistorySize());
+
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(args.pointerProperties[i].id, motionEvent.getPointerId(i));
+        EXPECT_EQ(args.pointerProperties[i].toolType, motionEvent.getToolType(i));
+
+        const auto& pc = args.pointerCoords[i];
+        EXPECT_EQ(pc, motionEvent.getSamplePointerCoords()[i]);
+
+        EXPECT_NEAR(pc.getX() * args.rawXScale + args.rawXOffset, motionEvent.getRawX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.rawYScale + args.rawYOffset, motionEvent.getRawY(i), EPSILON);
+        EXPECT_NEAR(pc.getX() * args.xScale + args.xOffset, motionEvent.getX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.yScale + args.yOffset, motionEvent.getY(i), EPSILON);
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent.getPressure(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent.getSize(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent.getTouchMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent.getTouchMinor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent.getToolMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent.getToolMinor(i));
+
+        // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
+        // "up", and the positive y direction is "down".
+        const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+        const float x = sinf(unscaledOrientation) * args.xScale;
+        const float y = -cosf(unscaledOrientation) * args.yScale;
+        EXPECT_EQ(atan2f(x, -y), motionEvent.getOrientation(i));
+    }
+}
+
+void publishMotionEvent(InputPublisher& publisher, const PublishMotionArgs& a) {
+    status_t status =
+            publisher.publishMotionEvent(a.seq, a.eventId, a.deviceId, a.source, a.displayId,
+                                         a.hmac, a.action, a.actionButton, a.flags, a.edgeFlags,
+                                         a.metaState, a.buttonState, a.classification, a.transform,
+                                         a.xPrecision, a.yPrecision, a.xCursorPosition,
+                                         a.yCursorPosition, a.rawTransform, a.downTime, a.eventTime,
+                                         a.pointerCount, a.pointerProperties.data(),
+                                         a.pointerCoords.data());
+    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
+}
+
+void sendAndVerifyFinishedSignal(InputConsumer& consumer, InputPublisher& publisher, uint32_t seq,
+                                 nsecs_t publishTime) {
+    status_t status = consumer.sendFinishedSignal(seq, false);
+    ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
+    Result<InputPublisher::ConsumerResponse> result = publisher.receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_FALSE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
+            << "finished signal's consume time should be greater than publish time";
+}
+
+void waitUntilInputAvailable(const InputConsumer& inputConsumer) {
+    bool hasInput;
+    do {
+        // The probablyHasInput() can return false positive under rare circumstances uncontrollable
+        // by the tests. Re-request the availability in this case. Returning |false| for a long
+        // time is not intended, and would cause a test timeout.
+        hasInput = inputConsumer.probablyHasInput();
+    } while (!hasInput);
+}
+
 } // namespace
 
 class InputPublisherAndConsumerTest : public testing::Test {
 protected:
-    std::shared_ptr<InputChannel> mServerChannel, mClientChannel;
     std::unique_ptr<InputPublisher> mPublisher;
     std::unique_ptr<InputConsumer> mConsumer;
     PreallocatedInputEventFactory mEventFactory;
@@ -54,15 +229,15 @@
         status_t result = InputChannel::openInputChannelPair("channel name",
                 serverChannel, clientChannel);
         ASSERT_EQ(OK, result);
-        mServerChannel = std::move(serverChannel);
-        mClientChannel = std::move(clientChannel);
 
-        mPublisher = std::make_unique<InputPublisher>(mServerChannel);
-        mConsumer = std::make_unique<InputConsumer>(mClientChannel);
+        mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
+        mConsumer = std::make_unique<InputConsumer>(std::move(clientChannel));
     }
 
     void publishAndConsumeKeyEvent();
     void publishAndConsumeMotionStream();
+    void publishAndConsumeMotionDown(nsecs_t downTime);
+    void publishAndConsumeBatchedMotionMove(nsecs_t downTime);
     void publishAndConsumeFocusEvent();
     void publishAndConsumeCaptureEvent();
     void publishAndConsumeDragEvent();
@@ -73,24 +248,10 @@
 private:
     // The sequence number to use when publishing the next event
     uint32_t mSeq = 1;
-
-    void publishAndConsumeMotionEvent(
-            int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
-            int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
-            int32_t metaState, int32_t buttonState, MotionClassification classification,
-            float xScale, float yScale, float xOffset, float yOffset, float xPrecision,
-            float yPrecision, float xCursorPosition, float yCursorPosition, float rawXScale,
-            float rawYScale, float rawXOffset, float rawYOffset, nsecs_t downTime,
-            nsecs_t eventTime, const std::vector<PointerProperties>& pointerProperties,
-            const std::vector<PointerCoords>& pointerCoords);
 };
 
 TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
-    ASSERT_NE(nullptr, mPublisher->getChannel());
-    ASSERT_NE(nullptr, mConsumer->getChannel());
-    EXPECT_EQ(mServerChannel.get(), mPublisher->getChannel().get());
-    EXPECT_EQ(mClientChannel.get(), mConsumer->getChannel().get());
-    ASSERT_EQ(mPublisher->getChannel()->getConnectionToken(),
+    ASSERT_EQ(mPublisher->getChannel().getConnectionToken(),
               mConsumer->getChannel()->getConnectionToken());
 }
 
@@ -101,7 +262,7 @@
     int32_t eventId = InputEvent::nextId();
     constexpr int32_t deviceId = 1;
     constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
-    constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
+    constexpr ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
     constexpr std::array<uint8_t, 32> hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21,
                                               20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,
                                               9,  8,  7,  6,  5,  4,  3,  2,  1,  0};
@@ -121,11 +282,14 @@
     ASSERT_EQ(OK, status)
             << "publisher publishKeyEvent should return OK";
 
+    waitUntilInputAvailable(*mConsumer);
     uint32_t consumeSeq;
     InputEvent* event;
     status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status)
             << "consumer consume should return OK";
+    EXPECT_FALSE(mConsumer->probablyHasInput())
+            << "no events should be waiting after being consumed";
 
     ASSERT_TRUE(event != nullptr)
             << "consumer should have returned non-NULL event";
@@ -185,176 +349,51 @@
                                   Pointer{.id = 2, .x = 300, .y = 400}});
 }
 
-void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
-        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
-    constexpr int32_t deviceId = 1;
-    constexpr uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
-    constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
-    constexpr std::array<uint8_t, 32> hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
-                                              11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
-                                              22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
-    constexpr int32_t actionButton = 0;
-    int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+void InputPublisherAndConsumerTest::publishAndConsumeMotionDown(nsecs_t downTime) {
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+}
 
-    if (action == AMOTION_EVENT_ACTION_CANCEL) {
-        flags |= AMOTION_EVENT_FLAG_CANCELED;
-    }
-    const size_t pointerCount = pointers.size();
-    constexpr int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
-    constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
-    constexpr int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
-    constexpr MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE;
-    constexpr float xScale = 2;
-    constexpr float yScale = 3;
-    constexpr float xOffset = -10;
-    constexpr float yOffset = -20;
-    constexpr float rawXScale = 4;
-    constexpr float rawYScale = -5;
-    constexpr float rawXOffset = -11;
-    constexpr float rawYOffset = 42;
-    constexpr float xPrecision = 0.25;
-    constexpr float yPrecision = 0.5;
-    constexpr float xCursorPosition = 1.3;
-    constexpr float yCursorPosition = 50.6;
+void InputPublisherAndConsumerTest::publishAndConsumeBatchedMotionMove(nsecs_t downTime) {
+    uint32_t seq = mSeq++;
+    const std::vector<Pointer> pointers = {Pointer{.id = 0, .x = 20, .y = 30}};
+    PublishMotionArgs args(AMOTION_EVENT_ACTION_MOVE, downTime, pointers, seq);
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, args);
 
-    const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    std::vector<PointerProperties> pointerProperties;
-    std::vector<PointerCoords> pointerCoords;
-    for (size_t i = 0; i < pointerCount; i++) {
-        pointerProperties.push_back({});
-        pointerProperties[i].clear();
-        pointerProperties[i].id = pointers[i].id;
-        pointerProperties[i].toolType = ToolType::FINGER;
-
-        pointerCoords.push_back({});
-        pointerCoords[i].clear();
-        pointerCoords[i].isResampled = pointers[i].isResampled;
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
-    }
-
-    publishAndConsumeMotionEvent(deviceId, source, displayId, hmac, action, actionButton, flags,
-                                 edgeFlags, metaState, buttonState, classification, xScale, yScale,
-                                 xOffset, yOffset, xPrecision, yPrecision, xCursorPosition,
-                                 yCursorPosition, rawXScale, rawYScale, rawXOffset, rawYOffset,
-                                 downTime, eventTime, pointerProperties, pointerCoords);
+    // Consume leaving a batch behind.
+    uint32_t consumeSeq;
+    InputEvent* event;
+    status_t status = mConsumer->consume(&mEventFactory,
+                                         /*consumeBatches=*/false, -1, &consumeSeq, &event);
+    ASSERT_EQ(WOULD_BLOCK, status)
+            << "consumer consume should return WOULD_BLOCK when a new batch is started";
+    ASSERT_TRUE(mConsumer->hasPendingBatch()) << "consume should have created a batch";
+    EXPECT_TRUE(mConsumer->probablyHasInput())
+            << "should deterministically have input because there is a batch";
+    sendAndVerifyFinishedSignal(*mConsumer, *mPublisher, seq, publishTime);
 }
 
 void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
-        int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
-        int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
-        int32_t buttonState, MotionClassification classification, float xScale, float yScale,
-        float xOffset, float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
-        float yCursorPosition, float rawXScale, float rawYScale, float rawXOffset, float rawYOffset,
-        nsecs_t downTime, nsecs_t eventTime,
-        const std::vector<PointerProperties>& pointerProperties,
-        const std::vector<PointerCoords>& pointerCoords) {
-    const uint32_t seq = mSeq++;
-    const int32_t eventId = InputEvent::nextId();
-    ui::Transform transform;
-    transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
-    ui::Transform rawTransform;
-    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
-
-    status_t status;
-    ASSERT_EQ(pointerProperties.size(), pointerCoords.size());
-    const size_t pointerCount = pointerProperties.size();
-    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
-                                            actionButton, flags, edgeFlags, metaState, buttonState,
-                                            classification, transform, xPrecision, yPrecision,
-                                            xCursorPosition, yCursorPosition, rawTransform,
-                                            downTime, eventTime, pointerCount,
-                                            pointerProperties.data(), pointerCoords.data());
-    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
+        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
+    uint32_t seq = mSeq++;
+    PublishMotionArgs args(action, downTime, pointers, seq);
+    nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, args);
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
-    ASSERT_EQ(OK, status)
-            << "consumer consume should return OK";
-
+    status_t status =
+            mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
+    ASSERT_EQ(OK, status) << "consumer consume should return OK";
     ASSERT_TRUE(event != nullptr)
             << "consumer should have returned non-NULL event";
     ASSERT_EQ(InputEventType::MOTION, event->getType())
             << "consumer should have returned a motion event";
-
-    MotionEvent* motionEvent = static_cast<MotionEvent*>(event);
     EXPECT_EQ(seq, consumeSeq);
-    EXPECT_EQ(eventId, motionEvent->getId());
-    EXPECT_EQ(deviceId, motionEvent->getDeviceId());
-    EXPECT_EQ(source, motionEvent->getSource());
-    EXPECT_EQ(displayId, motionEvent->getDisplayId());
-    EXPECT_EQ(hmac, motionEvent->getHmac());
-    EXPECT_EQ(action, motionEvent->getAction());
-    EXPECT_EQ(flags, motionEvent->getFlags());
-    EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags());
-    EXPECT_EQ(metaState, motionEvent->getMetaState());
-    EXPECT_EQ(buttonState, motionEvent->getButtonState());
-    EXPECT_EQ(classification, motionEvent->getClassification());
-    EXPECT_EQ(transform, motionEvent->getTransform());
-    EXPECT_EQ(xOffset, motionEvent->getXOffset());
-    EXPECT_EQ(yOffset, motionEvent->getYOffset());
-    EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
-    EXPECT_EQ(yPrecision, motionEvent->getYPrecision());
-    EXPECT_NEAR(xCursorPosition, motionEvent->getRawXCursorPosition(), EPSILON);
-    EXPECT_NEAR(yCursorPosition, motionEvent->getRawYCursorPosition(), EPSILON);
-    EXPECT_NEAR(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition(), EPSILON);
-    EXPECT_NEAR(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition(), EPSILON);
-    EXPECT_EQ(rawTransform, motionEvent->getRawTransform());
-    EXPECT_EQ(downTime, motionEvent->getDownTime());
-    EXPECT_EQ(eventTime, motionEvent->getEventTime());
-    EXPECT_EQ(pointerCount, motionEvent->getPointerCount());
-    EXPECT_EQ(0U, motionEvent->getHistorySize());
 
-    for (size_t i = 0; i < pointerCount; i++) {
-        SCOPED_TRACE(i);
-        EXPECT_EQ(pointerProperties[i].id, motionEvent->getPointerId(i));
-        EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i));
-
-        const auto& pc = pointerCoords[i];
-        EXPECT_EQ(pc, motionEvent->getSamplePointerCoords()[i]);
-
-        EXPECT_NEAR(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i), EPSILON);
-        EXPECT_NEAR(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i), EPSILON);
-        EXPECT_NEAR(pc.getX() * xScale + xOffset, motionEvent->getX(i), EPSILON);
-        EXPECT_NEAR(pc.getY() * yScale + yOffset, motionEvent->getY(i), EPSILON);
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent->getPressure(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent->getSize(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent->getTouchMajor(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent->getTouchMinor(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent->getToolMajor(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent->getToolMinor(i));
-
-        // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
-        // "up", and the positive y direction is "down".
-        const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
-        const float x = sinf(unscaledOrientation) * xScale;
-        const float y = -cosf(unscaledOrientation) * yScale;
-        EXPECT_EQ(atan2f(x, -y), motionEvent->getOrientation(i));
-    }
-
-    status = mConsumer->sendFinishedSignal(seq, false);
-    ASSERT_EQ(OK, status)
-            << "consumer sendFinishedSignal should return OK";
-
-    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
-    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
-    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
-    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
-    ASSERT_EQ(seq, finish.seq)
-            << "receiveConsumerResponse should have returned the original sequence number";
-    ASSERT_FALSE(finish.handled)
-            << "receiveConsumerResponse should have set handled to consumer's reply";
-    ASSERT_GE(finish.consumeTime, publishTime)
-            << "finished signal's consume time should be greater than publish time";
+    verifyArgsEqualToEvent(args, static_cast<const MotionEvent&>(*event));
+    sendAndVerifyFinishedSignal(*mConsumer, *mPublisher, seq, publishTime);
 }
 
 void InputPublisherAndConsumerTest::publishAndConsumeFocusEvent() {
@@ -546,6 +585,15 @@
     ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
 }
 
+TEST_F(InputPublisherAndConsumerTest, PublishMotionMoveEvent_EndToEnd) {
+    // Publish a DOWN event before MOVE to pass the InputVerifier checks.
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionDown(downTime));
+
+    // Publish the MOVE event and check expectations.
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeBatchedMotionMove(downTime));
+}
+
 TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) {
     ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
 }
@@ -574,13 +622,13 @@
 
     ui::Transform identityTransform;
     status =
-            mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                           0, 0, 0, MotionClassification::NONE, identityTransform,
-                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+            mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                            0, 0, pointerCount, pointerProperties, pointerCoords);
-    ASSERT_EQ(BAD_VALUE, status)
-            << "publisher publishMotionEvent should return BAD_VALUE";
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) {
@@ -591,17 +639,17 @@
 
     ui::Transform identityTransform;
     status =
-            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                           0, 0, 0, MotionClassification::NONE, identityTransform,
-                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                            0, 0, pointerCount, pointerProperties, pointerCoords);
-    ASSERT_EQ(BAD_VALUE, status)
-            << "publisher publishMotionEvent should return BAD_VALUE";
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
 }
 
 TEST_F(InputPublisherAndConsumerTest,
-        PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
+       PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
     status_t status;
     const size_t pointerCount = MAX_POINTERS + 1;
     PointerProperties pointerProperties[pointerCount];
@@ -613,13 +661,13 @@
 
     ui::Transform identityTransform;
     status =
-            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                           0, 0, 0, MotionClassification::NONE, identityTransform,
-                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                            0, 0, pointerCount, pointerProperties, pointerCoords);
-    ASSERT_EQ(BAD_VALUE, status)
-            << "publisher publishMotionEvent should return BAD_VALUE";
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
index 31cc145..cc41eeb 100644
--- a/libs/input/tests/MotionPredictorMetricsManager_test.cpp
+++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
@@ -238,14 +238,17 @@
 
 // --- Ground-truth-generation helper functions. ---
 
+// Generates numPoints ground truth points with values equal to those of the given
+// GroundTruthPoint, and with consecutive timestamps separated by the given inputInterval.
 std::vector<GroundTruthPoint> generateConstantGroundTruthPoints(
-        const GroundTruthPoint& groundTruthPoint, size_t numPoints) {
+        const GroundTruthPoint& groundTruthPoint, size_t numPoints,
+        nsecs_t inputInterval = TEST_PREDICTION_INTERVAL_NANOS) {
     std::vector<GroundTruthPoint> groundTruthPoints;
     nsecs_t timestamp = groundTruthPoint.timestamp;
     for (size_t i = 0; i < numPoints; ++i) {
         groundTruthPoints.emplace_back(groundTruthPoint);
         groundTruthPoints.back().timestamp = timestamp;
-        timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+        timestamp += inputInterval;
     }
     return groundTruthPoints;
 }
@@ -280,7 +283,8 @@
     const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
                                             .timestamp = TEST_INITIAL_TIMESTAMP};
     const std::vector<GroundTruthPoint> groundTruthPoints =
-            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3);
+            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3,
+                                              /*inputInterval=*/10);
 
     ASSERT_EQ(3u, groundTruthPoints.size());
     // First point.
@@ -290,11 +294,11 @@
     // Second point.
     EXPECT_EQ(groundTruthPoints[1].position, groundTruthPoint.position);
     EXPECT_EQ(groundTruthPoints[1].pressure, groundTruthPoint.pressure);
-    EXPECT_GT(groundTruthPoints[1].timestamp, groundTruthPoints[0].timestamp);
+    EXPECT_EQ(groundTruthPoints[1].timestamp, groundTruthPoint.timestamp + 10);
     // Third point.
     EXPECT_EQ(groundTruthPoints[2].position, groundTruthPoint.position);
     EXPECT_EQ(groundTruthPoints[2].pressure, groundTruthPoint.pressure);
-    EXPECT_GT(groundTruthPoints[2].timestamp, groundTruthPoints[1].timestamp);
+    EXPECT_EQ(groundTruthPoints[2].timestamp, groundTruthPoint.timestamp + 20);
 }
 
 TEST(GenerateCircularArcGroundTruthTest, StraightLineUpwards) {
@@ -333,16 +337,19 @@
 
 // --- Prediction-generation helper functions. ---
 
-// Creates a sequence of predictions with values equal to those of the given GroundTruthPoint.
-std::vector<PredictionPoint> generateConstantPredictions(const GroundTruthPoint& groundTruthPoint) {
+// Generates TEST_MAX_NUM_PREDICTIONS predictions with values equal to those of the given
+// GroundTruthPoint, and with consecutive timestamps separated by the given predictionInterval.
+std::vector<PredictionPoint> generateConstantPredictions(
+        const GroundTruthPoint& groundTruthPoint,
+        nsecs_t predictionInterval = TEST_PREDICTION_INTERVAL_NANOS) {
     std::vector<PredictionPoint> predictions;
-    nsecs_t predictionTimestamp = groundTruthPoint.timestamp + TEST_PREDICTION_INTERVAL_NANOS;
+    nsecs_t predictionTimestamp = groundTruthPoint.timestamp + predictionInterval;
     for (size_t j = 0; j < TEST_MAX_NUM_PREDICTIONS; ++j) {
         predictions.push_back(PredictionPoint{{.position = groundTruthPoint.position,
                                                .pressure = groundTruthPoint.pressure},
                                               .originTimestamp = groundTruthPoint.timestamp,
                                               .targetTimestamp = predictionTimestamp});
-        predictionTimestamp += TEST_PREDICTION_INTERVAL_NANOS;
+        predictionTimestamp += predictionInterval;
     }
     return predictions;
 }
@@ -375,8 +382,9 @@
 TEST(GeneratePredictionsTest, GenerateConstantPredictions) {
     const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
                                             .timestamp = TEST_INITIAL_TIMESTAMP};
+    const nsecs_t predictionInterval = 10;
     const std::vector<PredictionPoint> predictionPoints =
-            generateConstantPredictions(groundTruthPoint);
+            generateConstantPredictions(groundTruthPoint, predictionInterval);
 
     ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, predictionPoints.size());
     for (size_t i = 0; i < predictionPoints.size(); ++i) {
@@ -385,8 +393,7 @@
         EXPECT_THAT(predictionPoints[i].pressure, FloatNear(groundTruthPoint.pressure, 1e-6));
         EXPECT_EQ(predictionPoints[i].originTimestamp, groundTruthPoint.timestamp);
         EXPECT_EQ(predictionPoints[i].targetTimestamp,
-                  groundTruthPoint.timestamp +
-                          static_cast<nsecs_t>(i + 1) * TEST_PREDICTION_INTERVAL_NANOS);
+                  TEST_INITIAL_TIMESTAMP + static_cast<nsecs_t>(i + 1) * predictionInterval);
     }
 }
 
@@ -678,12 +685,9 @@
 //  • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements.
 //  • predictionPoints: the first index points to a vector of predictions corresponding to the
 //    source ground truth point with the same index.
-//     - The first element should be empty, because there are not expected to be predictions until
-//       we have received 2 ground truth points.
-//     - The last element may be empty, because there will be no future ground truth points to
-//       associate with those predictions (if not empty, it will be ignored).
+//     - For empty prediction vectors, MetricsManager::onPredict will not be called.
 //     - To test all prediction buckets, there should be at least TEST_MAX_NUM_PREDICTIONS non-empty
-//       prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and
+//       prediction vectors (that is, excluding the first and last). Thus, groundTruthPoints and
 //       predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2.
 //
 // When the function returns, outReportedAtomFields will contain the reported AtomFields.
@@ -697,19 +701,12 @@
                                                  createMockReportAtomFunction(
                                                          outReportedAtomFields));
 
-    // Validate structure of groundTruthPoints and predictionPoints.
-    ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
     ASSERT_GE(groundTruthPoints.size(), 2u);
-    ASSERT_EQ(predictionPoints[0].size(), 0u);
-    for (size_t i = 1; i + 1 < predictionPoints.size(); ++i) {
-        SCOPED_TRACE(testing::Message() << "i = " << i);
-        ASSERT_EQ(predictionPoints[i].size(), TEST_MAX_NUM_PREDICTIONS);
-    }
+    ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
 
-    // Pass ground truth points and predictions (for all except first and last ground truth).
     for (size_t i = 0; i < groundTruthPoints.size(); ++i) {
         metricsManager.onRecord(makeMotionEvent(groundTruthPoints[i]));
-        if ((i > 0) && (i + 1 < predictionPoints.size())) {
+        if (!predictionPoints[i].empty()) {
             metricsManager.onPredict(makeMotionEvent(predictionPoints[i]));
         }
     }
@@ -738,7 +735,7 @@
 // Perfect predictions test:
 //  • Input: constant input events, perfect predictions matching the input events.
 //  • Expectation: all error metrics should be zero, or NO_DATA_SENTINEL for "unreported" metrics.
-//    (For example, scale-invariant errors are only reported for the final time bucket.)
+//    (For example, scale-invariant errors are only reported for the last time bucket.)
 TEST(MotionPredictorMetricsManagerTest, ConstantGroundTruthPerfectPredictions) {
     GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10.0f, 20.0f), .pressure = 0.6f},
                                       .timestamp = TEST_INITIAL_TIMESTAMP};
@@ -977,5 +974,35 @@
     }
 }
 
+// Robustness test:
+//  • Input: input events separated by a significantly greater time interval than the interval
+//    between predictions.
+//  • Expectation: the MetricsManager should not crash in this case. (No assertions are made about
+//    the resulting metrics.)
+//
+// In practice, this scenario could arise either if the input and prediction intervals are
+// mismatched, or if input events are missing (dropped or skipped for some reason).
+TEST(MotionPredictorMetricsManagerTest, MismatchedInputAndPredictionInterval) {
+    // Create two ground truth points separated by MAX_NUM_PREDICTIONS * PREDICTION_INTERVAL,
+    // so that the second ground truth point corresponds to the last prediction bucket. This
+    // ensures that the scale-invariant error codepath will be run, giving full code coverage.
+    GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(0.0f, 0.0f), .pressure = 0.5f},
+                                      .timestamp = TEST_INITIAL_TIMESTAMP};
+    const nsecs_t inputInterval = TEST_MAX_NUM_PREDICTIONS * TEST_PREDICTION_INTERVAL_NANOS;
+    const std::vector<GroundTruthPoint> groundTruthPoints =
+            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/2, inputInterval);
+
+    // Create predictions separated by the prediction interval.
+    std::vector<std::vector<PredictionPoint>> predictionPoints;
+    for (size_t i = 0; i < groundTruthPoints.size(); ++i) {
+        predictionPoints.push_back(
+                generateConstantPredictions(groundTruthPoints[i], TEST_PREDICTION_INTERVAL_NANOS));
+    }
+
+    // Test that we can run the MetricsManager without crashing.
+    std::vector<AtomFields> reportedAtomFields;
+    runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
+}
+
 } // namespace
 } // namespace android
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index 3343114..d077760 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
+// TODO(b/331815574): Decouple this test from assumed config values.
 #include <chrono>
+#include <cmath>
 
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <input/MotionPredictor.h>
 
@@ -55,9 +58,10 @@
     }
 
     ui::Transform identityTransform;
-    event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, {0},
-                     action, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE,
-                     AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform,
+    event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS,
+                     ui::LogicalDisplayId::DEFAULT, {0}, action, /*actionButton=*/0, /*flags=*/0,
+                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
+                     MotionClassification::NONE, identityTransform,
                      /*xPrecision=*/0.1,
                      /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540,
                      identityTransform, /*downTime=*/100, eventTime.count(), pointerCount,
@@ -65,6 +69,108 @@
     return event;
 }
 
+TEST(JerkTrackerTest, JerkReadiness) {
+    JerkTracker jerkTracker(true);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/1, 25, 53);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/2, 30, 60);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/3, 35, 70);
+    EXPECT_TRUE(jerkTracker.jerkMagnitude());
+    jerkTracker.reset();
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/4, 30, 60);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+}
+
+TEST(JerkTrackerTest, JerkCalculationNormalizedDtTrue) {
+    JerkTracker jerkTracker(true);
+    jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
+    jerkTracker.pushSample(/*timestamp=*/1, 25, 53);
+    jerkTracker.pushSample(/*timestamp=*/2, 30, 60);
+    jerkTracker.pushSample(/*timestamp=*/3, 45, 70);
+    /**
+     * Jerk derivative table
+     * x:    20   25   30   45
+     * x':    5    5   15
+     * x'':   0   10
+     * x''': 10
+     *
+     * y:    50   53   60   70
+     * y':    3    7   10
+     * y'':   4    3
+     * y''': -1
+     */
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(10, -1));
+    jerkTracker.pushSample(/*timestamp=*/4, 20, 65);
+    /**
+     * (continuing from above table)
+     * x:    45 -> 20
+     * x':   15 -> -25
+     * x'':  10 -> -40
+     * x''': -50
+     *
+     * y:    70 -> 65
+     * y':   10 -> -5
+     * y'':  3 -> -15
+     * y''': -18
+     */
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(-50, -18));
+}
+
+TEST(JerkTrackerTest, JerkCalculationNormalizedDtFalse) {
+    JerkTracker jerkTracker(false);
+    jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
+    jerkTracker.pushSample(/*timestamp=*/10, 25, 53);
+    jerkTracker.pushSample(/*timestamp=*/20, 30, 60);
+    jerkTracker.pushSample(/*timestamp=*/30, 45, 70);
+    /**
+     * Jerk derivative table
+     * x:     20   25   30   45
+     * x':    .5   .5  1.5
+     * x'':    0   .1
+     * x''': .01
+     *
+     * y:       50   53   60   70
+     * y':      .3   .7    1
+     * y'':    .04  .03
+     * y''': -.001
+     */
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(.01, -.001));
+    jerkTracker.pushSample(/*timestamp=*/50, 20, 65);
+    /**
+     * (continuing from above table)
+     * x:    45 -> 20
+     * x':   1.5 -> -1.25 (delta above, divide by 20)
+     * x'':  .1 -> -.275 (delta above, divide by 10)
+     * x''': -.0375 (delta above, divide by 10)
+     *
+     * y:    70 -> 65
+     * y':   1 -> -.25 (delta above, divide by 20)
+     * y'':  .03 -> -.125 (delta above, divide by 10)
+     * y''': -.0155 (delta above, divide by 10)
+     */
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(-.0375, -.0155));
+}
+
+TEST(JerkTrackerTest, JerkCalculationAfterReset) {
+    JerkTracker jerkTracker(true);
+    jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
+    jerkTracker.pushSample(/*timestamp=*/1, 25, 53);
+    jerkTracker.pushSample(/*timestamp=*/2, 30, 60);
+    jerkTracker.pushSample(/*timestamp=*/3, 45, 70);
+    jerkTracker.pushSample(/*timestamp=*/4, 20, 65);
+    jerkTracker.reset();
+    jerkTracker.pushSample(/*timestamp=*/5, 20, 50);
+    jerkTracker.pushSample(/*timestamp=*/6, 25, 53);
+    jerkTracker.pushSample(/*timestamp=*/7, 30, 60);
+    jerkTracker.pushSample(/*timestamp=*/8, 45, 70);
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(10, -1));
+}
+
 TEST(MotionPredictorTest, IsPredictionAvailable) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
@@ -94,18 +200,14 @@
 TEST(MotionPredictorTest, FollowsGesture) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
+    predictor.record(getMotionEvent(DOWN, 3.75, 3, 20ms));
+    predictor.record(getMotionEvent(MOVE, 4.8, 3, 30ms));
+    predictor.record(getMotionEvent(MOVE, 6.2, 3, 40ms));
+    predictor.record(getMotionEvent(MOVE, 8, 3, 50ms));
+    EXPECT_NE(nullptr, predictor.predict(90 * NSEC_PER_MSEC));
 
-    // MOVE without a DOWN is ignored.
-    predictor.record(getMotionEvent(MOVE, 1, 3, 10ms));
-    EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC));
-
-    predictor.record(getMotionEvent(DOWN, 2, 5, 20ms));
-    predictor.record(getMotionEvent(MOVE, 2, 7, 30ms));
-    predictor.record(getMotionEvent(MOVE, 3, 9, 40ms));
-    EXPECT_NE(nullptr, predictor.predict(50 * NSEC_PER_MSEC));
-
-    predictor.record(getMotionEvent(UP, 4, 11, 50ms));
-    EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC));
+    predictor.record(getMotionEvent(UP, 10.25, 3, 60ms));
+    EXPECT_EQ(nullptr, predictor.predict(100 * NSEC_PER_MSEC));
 }
 
 TEST(MotionPredictorTest, MultipleDevicesNotSupported) {
@@ -147,6 +249,63 @@
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
 }
 
+TEST_WITH_FLAGS(
+        MotionPredictorTest, LowJerkNoPruning,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
+
+    // Jerk is low (0.05 normalized).
+    predictor.record(getMotionEvent(DOWN, 2, 7, 20ms));
+    predictor.record(getMotionEvent(MOVE, 2.75, 7, 30ms));
+    predictor.record(getMotionEvent(MOVE, 3.8, 7, 40ms));
+    predictor.record(getMotionEvent(MOVE, 5.2, 7, 50ms));
+    predictor.record(getMotionEvent(MOVE, 7, 7, 60ms));
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC);
+    EXPECT_NE(nullptr, predicted);
+    EXPECT_EQ(static_cast<size_t>(5), predicted->getHistorySize() + 1);
+}
+
+TEST_WITH_FLAGS(
+        MotionPredictorTest, HighJerkPredictionsPruned,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
+
+    // Jerk is incredibly high.
+    predictor.record(getMotionEvent(DOWN, 0, 5, 20ms));
+    predictor.record(getMotionEvent(MOVE, 0, 70, 30ms));
+    predictor.record(getMotionEvent(MOVE, 0, 139, 40ms));
+    predictor.record(getMotionEvent(MOVE, 0, 1421, 50ms));
+    predictor.record(getMotionEvent(MOVE, 0, 41233, 60ms));
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC);
+    EXPECT_EQ(nullptr, predicted);
+}
+
+TEST_WITH_FLAGS(
+        MotionPredictorTest, MediumJerkPredictionsSomePruned,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
+
+    // Jerk is medium (1.05 normalized, which is halfway between LOW_JANK and HIGH_JANK)
+    predictor.record(getMotionEvent(DOWN, 0, 5.2, 20ms));
+    predictor.record(getMotionEvent(MOVE, 0, 11.5, 30ms));
+    predictor.record(getMotionEvent(MOVE, 0, 22, 40ms));
+    predictor.record(getMotionEvent(MOVE, 0, 37.75, 50ms));
+    predictor.record(getMotionEvent(MOVE, 0, 59.8, 60ms));
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(82 * NSEC_PER_MSEC);
+    EXPECT_NE(nullptr, predicted);
+    // Halfway between LOW_JANK and HIGH_JANK means that half of the predictions
+    // will be pruned. If model prediction window is close enough to predict()
+    // call time window, then half of the model predictions (5/2 -> 2) will be
+    // ouputted.
+    EXPECT_EQ(static_cast<size_t>(3), predicted->getHistorySize() + 1);
+}
+
 using AtomFields = MotionPredictorMetricsManager::AtomFields;
 using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
 
diff --git a/libs/input/tests/TestHelpers.h b/libs/input/tests/TestHelpers.h
deleted file mode 100644
index 343d81f..0000000
--- a/libs/input/tests/TestHelpers.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2010 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 TESTHELPERS_H
-#define TESTHELPERS_H
-
-#include <unistd.h>
-
-#include <utils/threads.h>
-
-namespace android {
-
-class Pipe {
-public:
-    int sendFd;
-    int receiveFd;
-
-    Pipe() {
-        int fds[2];
-        ::pipe(fds);
-
-        receiveFd = fds[0];
-        sendFd = fds[1];
-    }
-
-    ~Pipe() {
-        if (sendFd != -1) {
-            ::close(sendFd);
-        }
-
-        if (receiveFd != -1) {
-            ::close(receiveFd);
-        }
-    }
-
-    status_t writeSignal() {
-        ssize_t nWritten = ::write(sendFd, "*", 1);
-        return nWritten == 1 ? 0 : -errno;
-    }
-
-    status_t readSignal() {
-        char buf[1];
-        ssize_t nRead = ::read(receiveFd, buf, 1);
-        return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno;
-    }
-};
-
-class DelayedTask : public Thread {
-    int mDelayMillis;
-
-public:
-    explicit DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { }
-
-protected:
-    virtual ~DelayedTask() { }
-
-    virtual void doTask() = 0;
-
-    virtual bool threadLoop() {
-        usleep(mDelayMillis * 1000);
-        doTask();
-        return false;
-    }
-};
-
-} // namespace android
-
-#endif // TESTHELPERS_H
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 1cb7f7b..2dc9fdb 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-#include "TestHelpers.h"
-
 #include <chrono>
 #include <vector>
 
 #include <attestation/HmacKeyManager.h>
 #include <gtest/gtest.h>
+#include <input/InputConsumer.h>
 #include <input/InputTransport.h>
 
 using namespace std::chrono_literals;
@@ -85,10 +84,11 @@
         ADD_FAILURE() << "Downtime should be equal to 0 (hardcoded for convenience)";
     }
     return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), /*deviceId=*/1,
-                                          AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, INVALID_HMAC,
-                                          action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0,
-                                          AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
-                                          identityTransform, /*xPrecision=*/0, /*yPrecision=*/0,
+                                          AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                                          INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0,
+                                          /*edgeFlags=*/0, AMETA_NONE, /*buttonState=*/0,
+                                          MotionClassification::NONE, identityTransform,
+                                          /*xPrecision=*/0, /*yPrecision=*/0,
                                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                           downTime, eventTime, properties.size(), properties.data(),
diff --git a/libs/input/tests/VelocityControl_test.cpp b/libs/input/tests/VelocityControl_test.cpp
new file mode 100644
index 0000000..63d64c6
--- /dev/null
+++ b/libs/input/tests/VelocityControl_test.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2024 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 <input/VelocityControl.h>
+
+#include <limits>
+
+#include <gtest/gtest.h>
+#include <input/AccelerationCurve.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+namespace {
+
+constexpr float EPSILON = 0.001;
+constexpr float COUNTS_PER_MM = 800 / 25.4;
+
+} // namespace
+
+class CurvedVelocityControlTest : public testing::Test {
+protected:
+    CurvedVelocityControl mCtrl;
+
+    void moveWithoutCheckingResult(nsecs_t eventTime, float deltaX, float deltaY) {
+        mCtrl.move(eventTime, &deltaX, &deltaY);
+    }
+
+    void moveAndCheckRatio(nsecs_t eventTime, const float deltaX, const float deltaY,
+                           float expectedRatio) {
+        float newDeltaX = deltaX, newDeltaY = deltaY;
+        mCtrl.move(eventTime, &newDeltaX, &newDeltaY);
+        ASSERT_NEAR(expectedRatio * deltaX, newDeltaX, EPSILON)
+                << "Expected ratio of " << expectedRatio << " in X, but actual ratio was "
+                << newDeltaX / deltaX;
+        ASSERT_NEAR(expectedRatio * deltaY, newDeltaY, EPSILON)
+                << "Expected ratio of " << expectedRatio << " in Y, but actual ratio was "
+                << newDeltaY / deltaY;
+    }
+};
+
+TEST_F(CurvedVelocityControlTest, SegmentSelection) {
+    // To make the maths simple, use a "curve" that's actually just a sequence of steps.
+    mCtrl.setCurve({
+            {10, 2, 0},
+            {20, 3, 0},
+            {30, 4, 0},
+            {std::numeric_limits<double>::infinity(), 5, 0},
+    });
+
+    // Establish a velocity of 16 mm/s.
+    moveWithoutCheckingResult(0, 0, 0);
+    moveWithoutCheckingResult(10'000'000, 0.16 * COUNTS_PER_MM, 0);
+    moveWithoutCheckingResult(20'000'000, 0.16 * COUNTS_PER_MM, 0);
+    moveWithoutCheckingResult(30'000'000, 0.16 * COUNTS_PER_MM, 0);
+    ASSERT_NO_FATAL_FAILURE(
+            moveAndCheckRatio(40'000'000, 0.16 * COUNTS_PER_MM, 0, /*expectedRatio=*/3));
+
+    // Establish a velocity of 50 mm/s.
+    mCtrl.reset();
+    moveWithoutCheckingResult(100'000'000, 0, 0);
+    moveWithoutCheckingResult(110'000'000, 0.50 * COUNTS_PER_MM, 0);
+    moveWithoutCheckingResult(120'000'000, 0.50 * COUNTS_PER_MM, 0);
+    moveWithoutCheckingResult(130'000'000, 0.50 * COUNTS_PER_MM, 0);
+    ASSERT_NO_FATAL_FAILURE(
+            moveAndCheckRatio(140'000'000, 0.50 * COUNTS_PER_MM, 0, /*expectedRatio=*/5));
+}
+
+TEST_F(CurvedVelocityControlTest, RatioDefaultsToFirstSegmentWhenVelocityIsUnknown) {
+    mCtrl.setCurve({
+            {10, 3, 0},
+            {20, 2, 0},
+            {std::numeric_limits<double>::infinity(), 4, 0},
+    });
+
+    // Only send two moves, which won't be enough for VelocityTracker to calculate a velocity from.
+    moveWithoutCheckingResult(0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(
+            moveAndCheckRatio(10'000'000, 0.25 * COUNTS_PER_MM, 0, /*expectedRatio=*/3));
+}
+
+TEST_F(CurvedVelocityControlTest, VelocityCalculatedUsingBothAxes) {
+    mCtrl.setCurve({
+            {8.0, 3, 0},
+            {8.1, 2, 0},
+            {std::numeric_limits<double>::infinity(), 4, 0},
+    });
+
+    // Establish a velocity of 8.06 (= √65 = √(7²+4²)) mm/s between the two axes.
+    moveWithoutCheckingResult(0, 0, 0);
+    moveWithoutCheckingResult(10'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM);
+    moveWithoutCheckingResult(20'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM);
+    moveWithoutCheckingResult(30'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM);
+    ASSERT_NO_FATAL_FAILURE(moveAndCheckRatio(40'000'000, 0.07 * COUNTS_PER_MM,
+                                              0.04 * COUNTS_PER_MM,
+                                              /*expectedRatio=*/2));
+}
+
+TEST_F(CurvedVelocityControlTest, ReciprocalTerm) {
+    mCtrl.setCurve({
+            {10, 2, 0},
+            {20, 3, -10},
+            {std::numeric_limits<double>::infinity(), 3, 0},
+    });
+
+    // Establish a velocity of 15 mm/s.
+    moveWithoutCheckingResult(0, 0, 0);
+    moveWithoutCheckingResult(10'000'000, 0, 0.15 * COUNTS_PER_MM);
+    moveWithoutCheckingResult(20'000'000, 0, 0.15 * COUNTS_PER_MM);
+    moveWithoutCheckingResult(30'000'000, 0, 0.15 * COUNTS_PER_MM);
+    // Expected ratio is 3 - 10 / 15 = 2.33333...
+    ASSERT_NO_FATAL_FAILURE(
+            moveAndCheckRatio(40'000'000, 0, 0.15 * COUNTS_PER_MM, /*expectedRatio=*/2.33333));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index f9ca280..f50a3cd 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -24,7 +24,6 @@
 #include <android-base/stringprintf.h>
 #include <attestation/HmacKeyManager.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/VelocityTracker.h>
 
 using std::literals::chrono_literals::operator""ms;
@@ -34,7 +33,7 @@
 
 namespace android {
 
-constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; // default display id
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; // default display id
 
 constexpr int32_t DEFAULT_POINTER_ID = 0; // pointer ID used for manually defined tests
 
@@ -156,7 +155,7 @@
         MotionEvent event;
         ui::Transform identityTransform;
         event.initialize(InputEvent::nextId(), /*deviceId=*/5, AINPUT_SOURCE_ROTARY_ENCODER,
-                         ADISPLAY_ID_NONE, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL,
+                         ui::LogicalDisplayId::INVALID, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL,
                          /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE,
                          /*buttonState=*/0, MotionClassification::NONE, identityTransform,
                          /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp
index 277d74d..df5fe9d 100644
--- a/libs/input/tests/VerifiedInputEvent_test.cpp
+++ b/libs/input/tests/VerifiedInputEvent_test.cpp
@@ -16,7 +16,6 @@
 
 #include <attestation/HmacKeyManager.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 
 namespace android {
@@ -24,7 +23,7 @@
 static KeyEvent getKeyEventWithFlags(int32_t flags) {
     KeyEvent event;
     event.initialize(InputEvent::nextId(), /*deviceId=*/2, AINPUT_SOURCE_GAMEPAD,
-                     ADISPLAY_ID_DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags,
+                     ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags,
                      AKEYCODE_BUTTON_X, /*scanCode=*/121, AMETA_ALT_ON, /*repeatCount=*/1,
                      /*downTime=*/1000, /*eventTime=*/2000);
     return event;
@@ -44,10 +43,11 @@
     ui::Transform transform;
     transform.set({2, 0, 4, 0, 3, 5, 0, 0, 1});
     ui::Transform identity;
-    event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT,
-                     INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, flags,
-                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
-                     MotionClassification::NONE, transform, /*xPrecision=*/0.1, /*yPrecision=*/0.2,
+    event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE,
+                     ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
+                     /*actionButton=*/0, flags, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE,
+                     /*buttonState=*/0, MotionClassification::NONE, transform, /*xPrecision=*/0.1,
+                     /*yPrecision=*/0.2,
                      /*xCursorPosition=*/280, /*yCursorPosition=*/540, identity, /*downTime=*/100,
                      /*eventTime=*/200, pointerCount, pointerProperties, pointerCoords);
     return event;
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 8f005a5..bed31e2 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -148,29 +148,31 @@
 void AChoreographer_postFrameCallback(AChoreographer* choreographer,
                                       AChoreographer_frameCallback callback, void* data) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0);
+            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0, CALLBACK_ANIMATION);
 }
 void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
                                              AChoreographer_frameCallback callback, void* data,
                                              long delayMillis) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis));
+            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis),
+                                       CALLBACK_ANIMATION);
 }
 void AChoreographer_postVsyncCallback(AChoreographer* choreographer,
                                       AChoreographer_vsyncCallback callback, void* data) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0);
+            ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0, CALLBACK_ANIMATION);
 }
 void AChoreographer_postFrameCallback64(AChoreographer* choreographer,
                                         AChoreographer_frameCallback64 callback, void* data) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0);
+            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0, CALLBACK_ANIMATION);
 }
 void AChoreographer_postFrameCallbackDelayed64(AChoreographer* choreographer,
                                                AChoreographer_frameCallback64 callback, void* data,
                                                uint32_t delayMillis) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis));
+            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis),
+                                       CALLBACK_ANIMATION);
 }
 void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
                                                 AChoreographer_refreshRateCallback callback,
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 342f5de..03f4f39 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -16,6 +16,7 @@
     default_applicable_licenses: [
         "frameworks_native_libs_nativedisplay_license",
     ],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 // Added automatically by a large-scale-change
@@ -33,7 +34,13 @@
 
 cc_library_headers {
     name: "libnativedisplay_headers",
+    host_supported: true,
     export_include_dirs: ["include"],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
 
 cc_library_shared {
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index e7b2195..5261287 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -113,6 +113,22 @@
                 AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
         "HAL and AHardwareBuffer pixel format don't match");
 
+static enum AHardwareBufferStatus filterStatus(status_t status) {
+    switch (status) {
+        case STATUS_OK:
+            return AHARDWAREBUFFER_STATUS_OK;
+        case STATUS_NO_MEMORY:
+            return AHARDWAREBUFFER_STATUS_NO_MEMORY;
+        case STATUS_BAD_VALUE:
+            return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+        case STATUS_UNKNOWN_TRANSACTION:
+        case STATUS_INVALID_OPERATION:
+            return AHARDWAREBUFFER_STATUS_UNSUPPORTED;
+        default:
+            return AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR;
+    }
+}
+
 // ----------------------------------------------------------------------------
 // Public functions
 // ----------------------------------------------------------------------------
@@ -511,6 +527,24 @@
     return AParcel_viewPlatformParcel(parcel)->write(*gb);
 }
 
+ADataSpace AHardwareBuffer_getDataSpace(const AHardwareBuffer* _Nonnull buffer) {
+    const GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
+    if (!gb) return ADATASPACE_UNKNOWN;
+    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
+    status_t status = gb->getDataspace(&dataspace);
+    if (status != OK) {
+        return ADATASPACE_UNKNOWN;
+    }
+    return static_cast<ADataSpace>(dataspace);
+}
+
+enum AHardwareBufferStatus AHardwareBuffer_setDataSpace(AHardwareBuffer* buffer,
+                                                        ADataSpace dataspace) {
+    GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
+    auto& mapper = GraphicBufferMapper::get();
+    return filterStatus(mapper.setDataspace(gb->handle, static_cast<ui::Dataspace>(dataspace)));
+}
+
 // ----------------------------------------------------------------------------
 // VNDK functions
 // ----------------------------------------------------------------------------
@@ -552,6 +586,56 @@
     return NO_ERROR;
 }
 
+enum AHardwareBufferStatus AHardwareBuffer_allocateWithOptions(
+        const AHardwareBuffer_Desc* desc, const AHardwareBufferLongOptions* additionalOptions,
+        size_t additionalOptionsSize, AHardwareBuffer** outBuffer) {
+    (void)additionalOptions;
+    (void)additionalOptionsSize;
+    if (!outBuffer || !desc) return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+    if (!AHardwareBuffer_isValidDescription(desc, /*log=*/true)) {
+        return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+    }
+
+    int format = AHardwareBuffer_convertToPixelFormat(desc->format);
+    uint64_t usage = AHardwareBuffer_convertToGrallocUsageBits(desc->usage);
+
+    std::vector<GraphicBufferAllocator::AdditionalOptions> extras;
+    extras.reserve(additionalOptionsSize);
+    for (size_t i = 0; i < additionalOptionsSize; i++) {
+        extras.push_back(GraphicBufferAllocator::AdditionalOptions{additionalOptions[i].name,
+                                                                   additionalOptions[i].value});
+    }
+
+    const auto extrasCount = extras.size();
+    auto gbuffer = sp<GraphicBuffer>::make(GraphicBufferAllocator::AllocationRequest{
+            .importBuffer = true,
+            .width = desc->width,
+            .height = desc->height,
+            .format = format,
+            .layerCount = desc->layers,
+            .usage = usage,
+            .requestorName = std::string("AHardwareBuffer pid [") + std::to_string(getpid()) + "]",
+            .extras = std::move(extras),
+    });
+
+    status_t err = gbuffer->initCheck();
+    if (err != 0 || gbuffer->handle == nullptr) {
+        if (err == NO_MEMORY) {
+        GraphicBuffer::dumpAllocationsToSystemLog();
+        }
+        ALOGE("GraphicBuffer(w=%u, h=%u, lc=%u, extrasCount=%zd) failed (%s), handle=%p",
+              desc->width, desc->height, desc->layers, extrasCount, strerror(-err),
+              gbuffer->handle);
+        return filterStatus(err == 0 ? UNKNOWN_ERROR : err);
+    }
+
+    *outBuffer = AHardwareBuffer_from_GraphicBuffer(gbuffer.get());
+
+    // Ensure the buffer doesn't get destroyed when the sp<> goes away.
+    AHardwareBuffer_acquire(*outBuffer);
+    return AHARDWAREBUFFER_STATUS_OK;
+}
+
 // ----------------------------------------------------------------------------
 // Helpers implementation
 // ----------------------------------------------------------------------------
@@ -652,12 +736,9 @@
     return ahardwarebuffer_format;
 }
 
+// TODO: Remove, this is just to make an overly aggressive ABI checker happy
 int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer) {
-    GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
-    auto& mapper = GraphicBufferMapper::get();
-    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
-    mapper.getDataspace(gb->handle, &dataspace);
-    return static_cast<int32_t>(dataspace);
+    return ::AHardwareBuffer_getDataSpace(buffer);
 }
 
 uint64_t AHardwareBuffer_convertToGrallocUsageBits(uint64_t usage) {
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index dd5958d..f97eed5 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -152,31 +152,56 @@
 
 int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpace) {
     static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN));
-    static_assert(static_cast<int>(STANDARD_MASK) == static_cast<int>(HAL_DATASPACE_STANDARD_MASK));
-    static_assert(static_cast<int>(STANDARD_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED));
-    static_assert(static_cast<int>(STANDARD_BT709) == static_cast<int>(HAL_DATASPACE_STANDARD_BT709));
-    static_assert(static_cast<int>(STANDARD_BT601_625) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625));
-    static_assert(static_cast<int>(STANDARD_BT601_625_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED));
-    static_assert(static_cast<int>(STANDARD_BT601_525) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525));
-    static_assert(static_cast<int>(STANDARD_BT601_525_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED));
-    static_assert(static_cast<int>(STANDARD_BT470M) == static_cast<int>(HAL_DATASPACE_STANDARD_BT470M));
-    static_assert(static_cast<int>(STANDARD_FILM) == static_cast<int>(HAL_DATASPACE_STANDARD_FILM));
-    static_assert(static_cast<int>(STANDARD_DCI_P3) == static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3));
-    static_assert(static_cast<int>(STANDARD_ADOBE_RGB) == static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB));
-    static_assert(static_cast<int>(TRANSFER_MASK) == static_cast<int>(HAL_DATASPACE_TRANSFER_MASK));
-    static_assert(static_cast<int>(TRANSFER_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED));
-    static_assert(static_cast<int>(TRANSFER_LINEAR) == static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR));
-    static_assert(static_cast<int>(TRANSFER_SMPTE_170M) == static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_2) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_6) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_8) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8));
-    static_assert(static_cast<int>(TRANSFER_ST2084) == static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084));
-    static_assert(static_cast<int>(TRANSFER_HLG) == static_cast<int>(HAL_DATASPACE_TRANSFER_HLG));
-    static_assert(static_cast<int>(RANGE_MASK) == static_cast<int>(HAL_DATASPACE_RANGE_MASK));
-    static_assert(static_cast<int>(RANGE_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED));
-    static_assert(static_cast<int>(RANGE_FULL) == static_cast<int>(HAL_DATASPACE_RANGE_FULL));
-    static_assert(static_cast<int>(RANGE_LIMITED) == static_cast<int>(HAL_DATASPACE_RANGE_LIMITED));
-    static_assert(static_cast<int>(RANGE_EXTENDED) == static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_MASK));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT709) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT709));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_625) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_625_UNADJUSTED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_525) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_525_UNADJUSTED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT470M) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT470M));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_FILM) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_FILM));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_DCI_P3) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_ADOBE_RGB) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_MASK));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_LINEAR) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_SMPTE_170M) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_2) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_6) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_8) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_ST2084) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_HLG) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_HLG));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_MASK));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_FULL) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_FULL));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_LIMITED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_LIMITED));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_EXTENDED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED));
     static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB));
     static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB));
     static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index bc0bfc5..8558074 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -16,6 +16,7 @@
     default_applicable_licenses: [
         "frameworks_native_libs_nativewindow_license",
     ],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 // Added automatically by a large-scale-change
@@ -53,6 +54,11 @@
         "test_com.android.media.swcodec",
     ],
     host_supported: true,
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
 
 ndk_library {
diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
index 880c694..f145a2f 100644
--- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
+++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
@@ -27,8 +27,8 @@
 
 #include <stdint.h>
 
-struct AHardwareBuffer;
-struct AHardwareBuffer_Desc;
+#include <vndk/hardware_buffer.h>
+
 struct ANativeWindowBuffer;
 
 namespace android {
@@ -46,11 +46,6 @@
 // convert HAL format to AHardwareBuffer format (note: this is a no-op)
 uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t format);
 
-// retrieves a dataspace from the AHardwareBuffer metadata, if the device
-// support gralloc metadata. Returns UNKNOWN if gralloc metadata is not
-// supported.
-int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer);
-
 // convert AHardwareBuffer usage bits to HAL usage bits (note: this is a no-op)
 uint64_t AHardwareBuffer_convertFromGrallocUsageBits(uint64_t usage);
 
diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h
index 9f8ae86..8056d9a 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -64,7 +64,7 @@
      * Defines the chromaticity coordinates of the source primaries in terms of
      * the CIE 1931 definition of x and y specified in ISO 11664-1.
      */
-    STANDARD_MASK = 63 << 16,
+    ADATASPACE_STANDARD_MASK = 63 << 16,
 
     /**
      * Chromacity coordinates are unknown or are determined by the application.
@@ -79,7 +79,7 @@
      * For all other formats standard is undefined, and implementations should use
      * an appropriate standard for the data represented.
      */
-    STANDARD_UNSPECIFIED = 0 << 16,
+    ADATASPACE_STANDARD_UNSPECIFIED = 0 << 16,
 
     /**
      * <pre>
@@ -92,7 +92,7 @@
      * Use the unadjusted KR = 0.2126, KB = 0.0722 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT709 = 1 << 16,
+    ADATASPACE_STANDARD_BT709 = 1 << 16,
 
     /**
      * <pre>
@@ -107,7 +107,7 @@
      *  to minimize the color shift into RGB space that uses BT.709
      *  primaries.
      */
-    STANDARD_BT601_625 = 2 << 16,
+    ADATASPACE_STANDARD_BT601_625 = 2 << 16,
 
     /**
      * <pre>
@@ -120,7 +120,7 @@
      * Use the unadjusted KR = 0.222, KB = 0.071 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT601_625_UNADJUSTED = 3 << 16,
+    ADATASPACE_STANDARD_BT601_625_UNADJUSTED = 3 << 16,
 
     /**
      * <pre>
@@ -135,7 +135,7 @@
      *  to minimize the color shift into RGB space that uses BT.709
      *  primaries.
      */
-    STANDARD_BT601_525 = 4 << 16,
+    ADATASPACE_STANDARD_BT601_525 = 4 << 16,
 
     /**
      * <pre>
@@ -148,7 +148,7 @@
      * Use the unadjusted KR = 0.212, KB = 0.087 luminance interpretation
      * for RGB conversion (as in SMPTE 240M).
      */
-    STANDARD_BT601_525_UNADJUSTED = 5 << 16,
+    ADATASPACE_STANDARD_BT601_525_UNADJUSTED = 5 << 16,
 
     /**
      * <pre>
@@ -161,7 +161,7 @@
      * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT2020 = 6 << 16,
+    ADATASPACE_STANDARD_BT2020 = 6 << 16,
 
     /**
      * <pre>
@@ -174,7 +174,7 @@
      * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
      * for RGB conversion using the linear domain.
      */
-    STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16,
+    ADATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16,
 
     /**
      * <pre>
@@ -187,7 +187,7 @@
      * Use the unadjusted KR = 0.30, KB = 0.11 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT470M = 8 << 16,
+    ADATASPACE_STANDARD_BT470M = 8 << 16,
 
     /**
      * <pre>
@@ -200,7 +200,7 @@
      * Use the unadjusted KR = 0.254, KB = 0.068 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_FILM = 9 << 16,
+    ADATASPACE_STANDARD_FILM = 9 << 16,
 
     /**
      * SMPTE EG 432-1 and SMPTE RP 431-2. (DCI-P3)
@@ -211,7 +211,7 @@
      *  red             0.680   0.320
      *  white (D65)     0.3127  0.3290</pre>
      */
-    STANDARD_DCI_P3 = 10 << 16,
+    ADATASPACE_STANDARD_DCI_P3 = 10 << 16,
 
     /**
      * Adobe RGB
@@ -222,7 +222,7 @@
      *  red             0.640   0.330
      *  white (D65)     0.3127  0.3290</pre>
      */
-    STANDARD_ADOBE_RGB = 11 << 16,
+    ADATASPACE_STANDARD_ADOBE_RGB = 11 << 16,
 
     /**
      * Transfer aspect
@@ -237,7 +237,7 @@
      * component. Implementation may apply the transfer function in RGB space
      * for all pixel formats if desired.
      */
-    TRANSFER_MASK = 31 << 22,
+    ADATASPACE_TRANSFER_MASK = 31 << 22,
 
     /**
      * Transfer characteristics are unknown or are determined by the
@@ -245,13 +245,13 @@
      *
      * Implementations should use the following transfer functions:
      *
-     * For YCbCr formats: use TRANSFER_SMPTE_170M
-     * For RGB formats: use TRANSFER_SRGB
+     * For YCbCr formats: use ADATASPACE_TRANSFER_SMPTE_170M
+     * For RGB formats: use ADATASPACE_TRANSFER_SRGB
      *
      * For all other formats transfer function is undefined, and implementations
      * should use an appropriate standard for the data represented.
      */
-    TRANSFER_UNSPECIFIED = 0 << 22,
+    ADATASPACE_TRANSFER_UNSPECIFIED = 0 << 22,
 
     /**
      * Linear transfer.
@@ -261,7 +261,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_LINEAR = 1 << 22,
+    ADATASPACE_TRANSFER_LINEAR = 1 << 22,
 
     /**
      * sRGB transfer.
@@ -272,7 +272,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_SRGB = 2 << 22,
+    ADATASPACE_TRANSFER_SRGB = 2 << 22,
 
     /**
      * SMPTE 170M transfer.
@@ -283,7 +283,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_SMPTE_170M = 3 << 22,
+    ADATASPACE_TRANSFER_SMPTE_170M = 3 << 22,
 
     /**
      * Display gamma 2.2.
@@ -293,7 +293,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_2 = 4 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_2 = 4 << 22,
 
     /**
      * Display gamma 2.6.
@@ -303,7 +303,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_6 = 5 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_6 = 5 << 22,
 
     /**
      * Display gamma 2.8.
@@ -313,7 +313,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_8 = 6 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_8 = 6 << 22,
 
     /**
      * SMPTE ST 2084 (Dolby Perceptual Quantizer).
@@ -329,7 +329,7 @@
      *         L = 1 corresponds to 10000 cd/m2
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_ST2084 = 7 << 22,
+    ADATASPACE_TRANSFER_ST2084 = 7 << 22,
 
     /**
      * ARIB STD-B67 Hybrid Log Gamma.
@@ -345,7 +345,7 @@
      *          to reference white level of 100 cd/m2
      *      E - corresponding electrical signal</pre>
      */
-    TRANSFER_HLG = 8 << 22,
+    ADATASPACE_TRANSFER_HLG = 8 << 22,
 
     /**
      * Range aspect
@@ -353,7 +353,7 @@
      * Defines the range of values corresponding to the unit range of 0-1.
      * This is defined for YCbCr only, but can be expanded to RGB space.
      */
-    RANGE_MASK = 7 << 27,
+    ADATASPACE_RANGE_MASK = 7 << 27,
 
     /**
      * Range is unknown or are determined by the application.  Implementations
@@ -366,13 +366,13 @@
      * For all other formats range is undefined, and implementations should use
      * an appropriate range for the data represented.
      */
-    RANGE_UNSPECIFIED = 0 << 27,
+    ADATASPACE_RANGE_UNSPECIFIED = 0 << 27,
 
     /**
      * Full range uses all values for Y, Cb and Cr from
      * 0 to 2^b-1, where b is the bit depth of the color format.
      */
-    RANGE_FULL = 1 << 27,
+    ADATASPACE_RANGE_FULL = 1 << 27,
 
     /**
      * Limited range uses values 16/256*2^b to 235/256*2^b for Y, and
@@ -387,7 +387,7 @@
      * Luma (Y) samples should range from 64 to 940, inclusive
      * Chroma (Cb, Cr) samples should range from 64 to 960, inclusive
      */
-    RANGE_LIMITED = 2 << 27,
+    ADATASPACE_RANGE_LIMITED = 2 << 27,
 
     /**
      * Extended range is used for scRGB. Intended for use with
@@ -396,7 +396,7 @@
      * color outside the sRGB gamut.
      * Used to blend / merge multiple dataspaces on a single display.
      */
-    RANGE_EXTENDED = 3 << 27,
+    ADATASPACE_RANGE_EXTENDED = 3 << 27,
 
     /**
      * scRGB linear encoding
@@ -411,7 +411,8 @@
      *
      * Uses extended range, linear transfer and BT.709 standard.
      */
-    ADATASPACE_SCRGB_LINEAR = 406913024, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_EXTENDED
+    ADATASPACE_SCRGB_LINEAR = 406913024, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_LINEAR |
+                                         // ADATASPACE_RANGE_EXTENDED
 
     /**
      * sRGB gamma encoding
@@ -426,7 +427,8 @@
      *
      * Uses full range, sRGB transfer BT.709 standard.
      */
-    ADATASPACE_SRGB = 142671872, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_FULL
+    ADATASPACE_SRGB = 142671872, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB |
+                                 // ADATASPACE_RANGE_FULL
 
     /**
      * scRGB
@@ -441,14 +443,16 @@
      *
      * Uses extended range, sRGB transfer and BT.709 standard.
      */
-    ADATASPACE_SCRGB = 411107328, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_EXTENDED
+    ADATASPACE_SCRGB = 411107328, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB |
+                                  // ADATASPACE_RANGE_EXTENDED
 
     /**
      * Display P3
      *
      * Uses full range, sRGB transfer and D65 DCI-P3 standard.
      */
-    ADATASPACE_DISPLAY_P3 = 143261696, // STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_FULL
+    ADATASPACE_DISPLAY_P3 = 143261696, // ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_SRGB |
+                                       // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -457,7 +461,8 @@
      *
      * Uses full range, SMPTE 2084 (PQ) transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_PQ = 163971072, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_FULL
+    ADATASPACE_BT2020_PQ = 163971072, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_ST2084 |
+                                      // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -466,7 +471,8 @@
      *
      * Uses limited range, SMPTE 2084 (PQ) transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_ITU_PQ = 298188800, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_LIMITED
+    ADATASPACE_BT2020_ITU_PQ = 298188800, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_ST2084
+                                          // | ADATASPACE_RANGE_LIMITED
 
     /**
      * Adobe RGB
@@ -476,7 +482,8 @@
      * Note: Application is responsible for gamma encoding the data as
      * a 2.2 gamma encoding is not supported in HW.
      */
-    ADATASPACE_ADOBE_RGB = 151715840, // STANDARD_ADOBE_RGB | TRANSFER_GAMMA2_2 | RANGE_FULL
+    ADATASPACE_ADOBE_RGB = 151715840, // ADATASPACE_STANDARD_ADOBE_RGB |
+                                      // ADATASPACE_TRANSFER_GAMMA2_2 | ADATASPACE_RANGE_FULL
 
     /**
      * JPEG File Interchange Format (JFIF)
@@ -485,7 +492,8 @@
      *
      * Uses full range, SMPTE 170M transfer and BT.601_625 standard.
      */
-    ADATASPACE_JFIF = 146931712, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_FULL
+    ADATASPACE_JFIF = 146931712, // ADATASPACE_STANDARD_BT601_625 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                 // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 601 (BT.601) - 625-line
@@ -494,7 +502,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.601_625 standard.
      */
-    ADATASPACE_BT601_625 = 281149440, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT601_625 = 281149440, // ADATASPACE_STANDARD_BT601_625 |
+                                      // ADATASPACE_TRANSFER_SMPTE_170M | ADATASPACE_RANGE_LIMITED
 
     /**
      * ITU-R Recommendation 601 (BT.601) - 525-line
@@ -503,7 +512,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.601_525 standard.
      */
-    ADATASPACE_BT601_525 = 281280512, // STANDARD_BT601_525 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT601_525 = 281280512, // ADATASPACE_STANDARD_BT601_525 |
+                                      // ADATASPACE_TRANSFER_SMPTE_170M | ADATASPACE_RANGE_LIMITED
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -512,7 +522,8 @@
      *
      * Uses full range, SMPTE 170M transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020 = 147193856, // STANDARD_BT2020 | TRANSFER_SMPTE_170M | RANGE_FULL
+    ADATASPACE_BT2020 = 147193856, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                   // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 709 (BT.709)
@@ -521,7 +532,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.709 standard.
      */
-    ADATASPACE_BT709 = 281083904, // STANDARD_BT709 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT709 = 281083904, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                  // ADATASPACE_RANGE_LIMITED
 
     /**
      * SMPTE EG 432-1 and SMPTE RP 431-2
@@ -533,7 +545,8 @@
      * Note: Application is responsible for gamma encoding the data as
      * a 2.6 gamma encoding is not supported in HW.
      */
-    ADATASPACE_DCI_P3 = 155844608, // STANDARD_DCI_P3 | TRANSFER_GAMMA2_6 | RANGE_FULL
+    ADATASPACE_DCI_P3 = 155844608, // ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_GAMMA2_6 |
+                                   // ADATASPACE_RANGE_FULL
 
     /**
      * sRGB linear encoding
@@ -547,21 +560,24 @@
      *
      * Uses full range, linear transfer and BT.709 standard.
      */
-    ADATASPACE_SRGB_LINEAR = 138477568, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_FULL
+    ADATASPACE_SRGB_LINEAR = 138477568, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_LINEAR |
+                                        // ADATASPACE_RANGE_FULL
 
     /**
      * Hybrid Log Gamma encoding
      *
      * Uses full range, hybrid log gamma transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_HLG = 168165376, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_FULL
+    ADATASPACE_BT2020_HLG = 168165376, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG |
+                                       // ADATASPACE_RANGE_FULL
 
     /**
      * ITU Hybrid Log Gamma encoding
      *
      * Uses limited range, hybrid log gamma transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_ITU_HLG = 302383104, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_LIMITED
+    ADATASPACE_BT2020_ITU_HLG = 302383104, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG |
+                                           // ADATASPACE_RANGE_LIMITED
 
     /**
      * Depth
@@ -575,7 +591,37 @@
      *
      * Embedded depth metadata following the dynamic depth specification.
      */
-    ADATASPACE_DYNAMIC_DEPTH = 4098
+    ADATASPACE_DYNAMIC_DEPTH = 4098,
+
+#ifndef ADATASPACE_SKIP_LEGACY_DEFINES
+    STANDARD_MASK = ADATASPACE_STANDARD_MASK,
+    STANDARD_UNSPECIFIED = ADATASPACE_STANDARD_UNSPECIFIED,
+    STANDARD_BT709 = ADATASPACE_STANDARD_BT709,
+    STANDARD_BT601_625 = ADATASPACE_STANDARD_BT601_625,
+    STANDARD_BT601_625_UNADJUSTED = ADATASPACE_STANDARD_BT601_625_UNADJUSTED,
+    STANDARD_BT601_525 = ADATASPACE_STANDARD_BT601_525,
+    STANDARD_BT601_525_UNADJUSTED = ADATASPACE_STANDARD_BT601_525_UNADJUSTED,
+    STANDARD_BT470M = ADATASPACE_STANDARD_BT470M,
+    STANDARD_BT2020 = ADATASPACE_STANDARD_BT2020,
+    STANDARD_FILM = ADATASPACE_STANDARD_FILM,
+    STANDARD_DCI_P3 = ADATASPACE_STANDARD_DCI_P3,
+    STANDARD_ADOBE_RGB = ADATASPACE_STANDARD_ADOBE_RGB,
+    TRANSFER_MASK = ADATASPACE_TRANSFER_MASK,
+    TRANSFER_UNSPECIFIED = ADATASPACE_TRANSFER_UNSPECIFIED,
+    TRANSFER_LINEAR = ADATASPACE_TRANSFER_LINEAR,
+    TRANSFER_SMPTE_170M = ADATASPACE_TRANSFER_SMPTE_170M,
+    TRANSFER_GAMMA2_2 = ADATASPACE_TRANSFER_GAMMA2_2,
+    TRANSFER_GAMMA2_6 = ADATASPACE_TRANSFER_GAMMA2_6,
+    TRANSFER_GAMMA2_8 = ADATASPACE_TRANSFER_GAMMA2_8,
+    TRANSFER_SRGB = ADATASPACE_TRANSFER_SRGB,
+    TRANSFER_ST2084 = ADATASPACE_TRANSFER_ST2084,
+    TRANSFER_HLG = ADATASPACE_TRANSFER_HLG,
+    RANGE_MASK = ADATASPACE_RANGE_MASK,
+    RANGE_UNSPECIFIED = ADATASPACE_RANGE_UNSPECIFIED,
+    RANGE_FULL = ADATASPACE_RANGE_FULL,
+    RANGE_LIMITED = ADATASPACE_RANGE_LIMITED,
+    RANGE_EXTENDED = ADATASPACE_RANGE_EXTENDED,
+#endif
 };
 
 __END_DECLS
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index 21798d0..6fcb3a4 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -46,9 +46,16 @@
 #define ANDROID_HARDWARE_BUFFER_H
 
 #include <android/rect.h>
+#define ADATASPACE_SKIP_LEGACY_DEFINES
+#include <android/data_space.h>
+#undef ADATASPACE_SKIP_LEGACY_DEFINES
 #include <inttypes.h>
 #include <sys/cdefs.h>
 
+#if !defined(__INTRODUCED_IN)
+#define __INTRODUCED_IN(__api_level) /* nothing */
+#endif
+
 __BEGIN_DECLS
 
 // clang-format off
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index a98ea86..33c303a 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -41,6 +41,8 @@
 #include <system/graphics.h>
 #include <unistd.h>
 
+#include <vndk/hardware_buffer.h>
+
 // system/window.h is a superset of the vndk and apex apis
 #include <apex/window.h>
 #include <vndk/window.h>
@@ -257,6 +259,7 @@
     NATIVE_WINDOW_SET_QUERY_INTERCEPTOR           = 47,    /* private */
     NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO         = 48,    /* private */
     NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2         = 49,    /* private */
+    NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS  = 50,
     // clang-format on
 };
 
@@ -1095,10 +1098,19 @@
     ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL = 3,
 
     /**
+     * Indicates that, as a result of a user interaction, an animation is likely to start.
+     * This category is a signal that a user interaction heuristic determined the need of a
+     * high refresh rate, and is not an explicit request from the app.
+     * As opposed to FRAME_RATE_CATEGORY_HIGH, this vote may be ignored in favor of
+     * more explicit votes.
+     */
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH_HINT = 4,
+
+    /**
      * Indicates a frame rate suitable for animations that require a high frame rate, which may
      * increase smoothness but may also increase power usage.
      */
-    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 5
 };
 
 /*
@@ -1173,6 +1185,26 @@
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineInfo);
 }
 
+/**
+ * native_window_set_buffers_additional_options(..., ExtendableType* additionalOptions, size_t size)
+ * All buffers dequeued after this call will have the additionalOptions specified.
+ *
+ * This must only be called after api_connect, otherwise NO_INIT is returned. The options are
+ * cleared in api_disconnect & api_connect
+ *
+ * If IAllocator is not v2 or newer this method returns INVALID_OPERATION
+ *
+ * \return NO_ERROR on success.
+ * \return NO_INIT if no api is connected
+ * \return INVALID_OPERATION if additional option support is not available
+ */
+static inline int native_window_set_buffers_additional_options(
+        struct ANativeWindow* window, const AHardwareBufferLongOptions* additionalOptions,
+        size_t additionalOptionsSize) {
+    return window->perform(window, NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS, additionalOptions,
+                           additionalOptionsSize);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Candidates for APEX visibility
 // These functions are planned to be made stable for APEX modules, but have not
diff --git a/libs/nativewindow/include/vndk/hardware_buffer.h b/libs/nativewindow/include/vndk/hardware_buffer.h
index 21931bb..48fb02f 100644
--- a/libs/nativewindow/include/vndk/hardware_buffer.h
+++ b/libs/nativewindow/include/vndk/hardware_buffer.h
@@ -21,6 +21,7 @@
 #include <android/hardware_buffer.h>
 
 #include <cutils/native_handle.h>
+#include <errno.h>
 
 __BEGIN_DECLS
 
@@ -105,6 +106,76 @@
     AHARDWAREBUFFER_USAGE_CAMERA_MASK               = 6UL << 16,
 };
 
+/**
+ * Additional options for AHardwareBuffer_allocateWithOptions. These correspond to
+ * android.hardware.graphics.common.ExtendableType
+ */
+typedef struct {
+    const char* _Nonnull name;
+    int64_t value;
+} AHardwareBufferLongOptions;
+
+enum AHardwareBufferStatus : int32_t {
+    /* Success, no error */
+    AHARDWAREBUFFER_STATUS_OK = 0,
+    /* There's insufficient memory to satisfy the request */
+    AHARDWAREBUFFER_STATUS_NO_MEMORY = -ENOMEM,
+    /* The given argument is invalid */
+    AHARDWAREBUFFER_STATUS_BAD_VALUE = -EINVAL,
+    /* The requested operation is not supported by the device */
+    AHARDWAREBUFFER_STATUS_UNSUPPORTED = -ENOSYS,
+    /* An unknown error occurred */
+    AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR = (-2147483647 - 1),
+};
+
+/**
+ * Allocates a buffer that matches the passed AHardwareBuffer_Desc with additional options
+ *
+ * If allocation succeeds, the buffer can be used according to the
+ * usage flags specified in its description. If a buffer is used in ways
+ * not compatible with its usage flags, the results are undefined and
+ * may include program termination.
+ *
+ * @param desc The AHardwareBuffer_Desc that describes the allocation to request. Note that `stride`
+ *             is ignored.
+ * @param additionalOptions A pointer to an array of AHardwareBufferLongOptions with additional
+ *                          string key + long value options that may be specified. May be null if
+ *                          `additionalOptionsSize` is 0
+ * @param additionalOptionsSize The number of additional options to pass
+ * @param outBuffer The resulting buffer allocation
+ * @return AHARDWAREBUFFER_STATUS_OK on success
+ *         AHARDWAREBUFFER_STATUS_NO_MEMORY if there's insufficient resources for the allocation
+ *         AHARDWAREBUFFER_STATUS_BAD_VALUE if the provided description & options are not supported
+ *         by the device
+ *         AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR for any other error
+ * any reason. The returned buffer has a reference count of 1.
+ */
+enum AHardwareBufferStatus AHardwareBuffer_allocateWithOptions(
+        const AHardwareBuffer_Desc* _Nonnull desc,
+        const AHardwareBufferLongOptions* _Nullable additionalOptions, size_t additionalOptionsSize,
+        AHardwareBuffer* _Nullable* _Nonnull outBuffer) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Queries the dataspace of the given AHardwareBuffer.
+ *
+ * @param buffer The non-null buffer for which to query the Dataspace
+ * @return The dataspace of the buffer, or ADATASPACE_UNKNOWN if one hasn't been set
+ */
+enum ADataSpace AHardwareBuffer_getDataSpace(const AHardwareBuffer* _Nonnull buffer)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the dataspace of the given AHardwareBuffer
+ * @param buffer The non-null buffer for which to set the dataspace
+ * @param dataSpace The dataspace to set
+ * @return AHARDWAREBUFFER_STATUS_OK on success,
+ *         AHARDWAREBUFFER_STATUS_UNSUPPORTED if the device doesn't support setting the dataspace,
+ *         AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR for any other failure.
+ */
+enum AHardwareBufferStatus AHardwareBuffer_setDataSpace(AHardwareBuffer* _Nonnull buffer,
+                                                        enum ADataSpace dataSpace)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
 __END_DECLS
 
 #endif /* ANDROID_VNDK_NATIVEWINDOW_AHARDWAREBUFFER_H */
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index 95fc920..e29d5a6 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -2,6 +2,7 @@
   global:
     AHardwareBuffer_acquire;
     AHardwareBuffer_allocate;
+    AHardwareBuffer_allocateWithOptions; # llndk systemapi
     AHardwareBuffer_createFromHandle; # llndk systemapi
     AHardwareBuffer_describe;
     AHardwareBuffer_getId; # introduced=31
@@ -16,6 +17,8 @@
     AHardwareBuffer_unlock;
     AHardwareBuffer_readFromParcel; # introduced=34
     AHardwareBuffer_writeToParcel; # introduced=34
+    AHardwareBuffer_getDataSpace; # llndk systemapi
+    AHardwareBuffer_setDataSpace; # llndk systemapi
     ANativeWindowBuffer_getHardwareBuffer; # llndk
     ANativeWindow_OemStorageGet; # llndk
     ANativeWindow_OemStorageSet; # llndk
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index 798d804..97740db 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -16,6 +16,7 @@
     default_applicable_licenses: [
         "frameworks_native_libs_nativewindow_license",
     ],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 rust_bindgen {
diff --git a/libs/nativewindow/tests/AHardwareBufferTest.cpp b/libs/nativewindow/tests/AHardwareBufferTest.cpp
index ef863b6..136395a 100644
--- a/libs/nativewindow/tests/AHardwareBufferTest.cpp
+++ b/libs/nativewindow/tests/AHardwareBufferTest.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "AHardwareBuffer_test"
 //#define LOG_NDEBUG 0
 
+#include <android-base/properties.h>
+#include <android/data_space.h>
 #include <android/hardware/graphics/common/1.0/types.h>
 #include <gtest/gtest.h>
 #include <private/android/AHardwareBufferHelpers.h>
@@ -26,6 +28,10 @@
 using namespace android;
 using android::hardware::graphics::common::V1_0::BufferUsage;
 
+static bool IsCuttlefish() {
+    return ::android::base::GetProperty("ro.product.board", "") == "cutf";
+}
+
 static ::testing::AssertionResult BuildHexFailureMessage(uint64_t expected,
         uint64_t actual, const char* type) {
     std::ostringstream ss;
@@ -170,3 +176,83 @@
 
     EXPECT_NE(id1, id2);
 }
+
+TEST(AHardwareBufferTest, Allocate2NoExtras) {
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 1,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_BLOB,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    ASSERT_EQ(0, AHardwareBuffer_allocateWithOptions(&desc, nullptr, 0, &buffer));
+    uint64_t id = 0;
+    EXPECT_EQ(0, AHardwareBuffer_getId(buffer, &id));
+    EXPECT_NE(0, id);
+    AHardwareBuffer_Desc desc2{};
+    AHardwareBuffer_describe(buffer, &desc2);
+    EXPECT_EQ(desc.width, desc2.width);
+    EXPECT_EQ(desc.height, desc2.height);
+    EXPECT_GE(desc2.stride, desc2.width);
+
+    AHardwareBuffer_release(buffer);
+}
+
+TEST(AHardwareBufferTest, Allocate2WithExtras) {
+    if (!IsCuttlefish()) {
+        GTEST_SKIP() << "Unknown gralloc HAL, cannot test extras";
+    }
+
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 48,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    std::array<AHardwareBufferLongOptions, 1> extras = {{
+            {.name = "android.hardware.graphics.common.Dataspace", ADATASPACE_DISPLAY_P3},
+    }};
+    ASSERT_EQ(0, AHardwareBuffer_allocateWithOptions(&desc, extras.data(), extras.size(), &buffer));
+    uint64_t id = 0;
+    EXPECT_EQ(0, AHardwareBuffer_getId(buffer, &id));
+    EXPECT_NE(0, id);
+    AHardwareBuffer_Desc desc2{};
+    AHardwareBuffer_describe(buffer, &desc2);
+    EXPECT_EQ(desc.width, desc2.width);
+    EXPECT_EQ(desc.height, desc2.height);
+    EXPECT_GE(desc2.stride, desc2.width);
+
+    EXPECT_EQ(ADATASPACE_DISPLAY_P3, AHardwareBuffer_getDataSpace(buffer));
+
+    AHardwareBuffer_release(buffer);
+}
+
+TEST(AHardwareBufferTest, GetSetDataspace) {
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 48,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    ASSERT_EQ(0, AHardwareBuffer_allocate(&desc, &buffer));
+
+    EXPECT_EQ(ADATASPACE_UNKNOWN, AHardwareBuffer_getDataSpace(buffer));
+    AHardwareBufferStatus status = AHardwareBuffer_setDataSpace(buffer, ADATASPACE_DISPLAY_P3);
+    if (status != AHARDWAREBUFFER_STATUS_UNSUPPORTED) {
+        EXPECT_EQ(0, status);
+        EXPECT_EQ(ADATASPACE_DISPLAY_P3, AHardwareBuffer_getDataSpace(buffer));
+    }
+
+    AHardwareBuffer_release(buffer);
+}
\ No newline at end of file
diff --git a/libs/nativewindow/tests/Android.bp b/libs/nativewindow/tests/Android.bp
index 30737c1..a4ebcac 100644
--- a/libs/nativewindow/tests/Android.bp
+++ b/libs/nativewindow/tests/Android.bp
@@ -23,6 +23,7 @@
     default_applicable_licenses: [
         "frameworks_native_libs_nativewindow_license",
     ],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
@@ -31,6 +32,7 @@
         "device-tests",
     ],
     shared_libs: [
+        "libbase",
         "libgui",
         "liblog",
         "libnativewindow",
@@ -44,5 +46,8 @@
         "ANativeWindowTest.cpp",
         "c_compatibility.c",
     ],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
diff --git a/libs/nativewindow/tests/benchmark/Android.bp b/libs/nativewindow/tests/benchmark/Android.bp
index 6f844cf..b815d80 100644
--- a/libs/nativewindow/tests/benchmark/Android.bp
+++ b/libs/nativewindow/tests/benchmark/Android.bp
@@ -12,6 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
+}
+
 cc_defaults {
     name: "nativewindow_benchmark_defaults_cc",
     shared_libs: ["libnativewindow"],
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index ba2eb7d..c003111 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_defaults {
@@ -48,11 +49,19 @@
     static_libs: [
         "libshaders",
         "libtonemap",
+        "libsurfaceflinger_common",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
 }
 
+// Needed by FlagManager to access a #define.
+cc_library_static {
+    name: "librenderengine_includes",
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+}
+
 filegroup {
     name: "librenderengine_sources",
     srcs: [
@@ -74,10 +83,17 @@
         "skia/AutoBackendTexture.cpp",
         "skia/Cache.cpp",
         "skia/ColorSpaces.cpp",
+        "skia/GaneshVkRenderEngine.cpp",
+        "skia/GraphiteVkRenderEngine.cpp",
         "skia/GLExtensions.cpp",
         "skia/SkiaRenderEngine.cpp",
         "skia/SkiaGLRenderEngine.cpp",
         "skia/SkiaVkRenderEngine.cpp",
+        "skia/VulkanInterface.cpp",
+        "skia/compat/GaneshBackendTexture.cpp",
+        "skia/compat/GaneshGpuContext.cpp",
+        "skia/compat/GraphiteBackendTexture.cpp",
+        "skia/compat/GraphiteGpuContext.cpp",
         "skia/debug/CaptureTimer.cpp",
         "skia/debug/CommonPool.cpp",
         "skia/debug/SkiaCapture.cpp",
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 3e1ac33..1c60563 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -16,40 +16,45 @@
 
 #include <renderengine/RenderEngine.h>
 
-#include <cutils/properties.h>
-#include <log/log.h>
 #include "renderengine/ExternalTexture.h"
+#include "skia/GaneshVkRenderEngine.h"
+#include "skia/GraphiteVkRenderEngine.h"
+#include "skia/SkiaGLRenderEngine.h"
 #include "threaded/RenderEngineThreaded.h"
 
-#include "skia/SkiaGLRenderEngine.h"
-#include "skia/SkiaVkRenderEngine.h"
+#include <cutils/properties.h>
+#include <log/log.h>
 
 namespace android {
 namespace renderengine {
 
 std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
-    switch (args.renderEngineType) {
-        case RenderEngineType::SKIA_GL:
-            ALOGD("RenderEngine with SkiaGL Backend");
-            return renderengine::skia::SkiaGLRenderEngine::create(args);
-        case RenderEngineType::SKIA_VK:
-            ALOGD("RenderEngine with SkiaVK Backend");
-            return renderengine::skia::SkiaVkRenderEngine::create(args);
-        case RenderEngineType::SKIA_GL_THREADED: {
-            ALOGD("Threaded RenderEngine with SkiaGL Backend");
-            return renderengine::threaded::RenderEngineThreaded::create(
-                    [args]() {
-                        return android::renderengine::skia::SkiaGLRenderEngine::create(args);
-                    },
-                    args.renderEngineType);
+    threaded::CreateInstanceFactory createInstanceFactory;
+
+    ALOGD("%sRenderEngine with %s Backend (%s)", args.threaded == Threaded::YES ? "Threaded " : "",
+          args.graphicsApi == GraphicsApi::GL ? "SkiaGL" : "SkiaVK",
+          args.skiaBackend == SkiaBackend::GANESH ? "Ganesh" : "Graphite");
+
+    if (args.skiaBackend == SkiaBackend::GRAPHITE) {
+        createInstanceFactory = [args]() {
+            return android::renderengine::skia::GraphiteVkRenderEngine::create(args);
+        };
+    } else { // GANESH
+        if (args.graphicsApi == GraphicsApi::VK) {
+            createInstanceFactory = [args]() {
+                return android::renderengine::skia::GaneshVkRenderEngine::create(args);
+            };
+        } else { // GL
+            createInstanceFactory = [args]() {
+                return android::renderengine::skia::SkiaGLRenderEngine::create(args);
+            };
         }
-        case RenderEngineType::SKIA_VK_THREADED:
-            ALOGD("Threaded RenderEngine with SkiaVK Backend");
-            return renderengine::threaded::RenderEngineThreaded::create(
-                    [args]() {
-                        return android::renderengine::skia::SkiaVkRenderEngine::create(args);
-                    },
-                    args.renderEngineType);
+    }
+
+    if (args.threaded == Threaded::YES) {
+        return renderengine::threaded::RenderEngineThreaded::create(createInstanceFactory);
+    } else {
+        return createInstanceFactory();
     }
 }
 
diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp
index 55c34cd..e1a6f6a 100644
--- a/libs/renderengine/benchmark/Android.bp
+++ b/libs/renderengine/benchmark/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_benchmark {
@@ -37,6 +38,7 @@
     static_libs: [
         "librenderengine",
         "libshaders",
+        "libsurfaceflinger_common",
         "libtonemap",
     ],
     cflags: [
@@ -54,6 +56,7 @@
         "libsync",
         "libui",
         "libutils",
+        "server_configurable_flags",
     ],
 
     data: ["resources/*"],
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index a7f1df9..05a2063 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -30,46 +30,6 @@
 using namespace android::renderengine;
 
 ///////////////////////////////////////////////////////////////////////////////
-//  Helpers for Benchmark::Apply
-///////////////////////////////////////////////////////////////////////////////
-
-std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) {
-    switch (type) {
-        case RenderEngine::RenderEngineType::SKIA_GL_THREADED:
-            return "skiaglthreaded";
-        case RenderEngine::RenderEngineType::SKIA_GL:
-            return "skiagl";
-        case RenderEngine::RenderEngineType::SKIA_VK:
-            return "skiavk";
-        case RenderEngine::RenderEngineType::SKIA_VK_THREADED:
-            return "skiavkthreaded";
-    }
-}
-
-/**
- * Passed (indirectly - see RunSkiaGLThreaded) to Benchmark::Apply to create a
- * Benchmark which specifies which RenderEngineType it uses.
- *
- * This simplifies calling ->Arg(type)->Arg(type) and provides strings to make
- * it obvious which version is being run.
- *
- * @param b The benchmark family
- * @param type The type of RenderEngine to use.
- */
-static void AddRenderEngineType(benchmark::internal::Benchmark* b,
-                                RenderEngine::RenderEngineType type) {
-    b->Arg(static_cast<int64_t>(type));
-    b->ArgName(RenderEngineTypeName(type));
-}
-
-/**
- * Run a benchmark once using SKIA_GL_THREADED.
- */
-static void RunSkiaGLThreaded(benchmark::internal::Benchmark* b) {
-    AddRenderEngineType(b, RenderEngine::RenderEngineType::SKIA_GL_THREADED);
-}
-
-///////////////////////////////////////////////////////////////////////////////
 //  Helpers for calling drawLayers
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -104,15 +64,17 @@
     return std::pair<uint32_t, uint32_t>(width, height);
 }
 
-static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) {
+static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::Threaded threaded,
+                                                        RenderEngine::GraphicsApi graphicsApi) {
     auto args = RenderEngineCreationArgs::Builder()
                         .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
                         .setImageCacheSize(1)
                         .setEnableProtectedContext(true)
                         .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
+                        .setBlurAlgorithm(renderengine::RenderEngine::BlurAlgorithm::KAWASE)
                         .setContextPriority(RenderEngine::ContextPriority::REALTIME)
-                        .setRenderEngineType(type)
+                        .setThreaded(threaded)
+                        .setGraphicsApi(graphicsApi)
                         .build();
     return RenderEngine::create(args);
 }
@@ -214,8 +176,11 @@
 //  Benchmarks
 ///////////////////////////////////////////////////////////////////////////////
 
-void BM_blur(benchmark::State& benchState) {
-    auto re = createRenderEngine(static_cast<RenderEngine::RenderEngineType>(benchState.range()));
+template <class... Args>
+void BM_blur(benchmark::State& benchState, Args&&... args) {
+    auto args_tuple = std::make_tuple(std::move(args)...);
+    auto re = createRenderEngine(static_cast<RenderEngine::Threaded>(std::get<0>(args_tuple)),
+                                 static_cast<RenderEngine::GraphicsApi>(std::get<1>(args_tuple)));
 
     // Initially use cpu access so we can decode into it with AImageDecoder.
     auto [width, height] = getDisplaySize();
@@ -259,4 +224,5 @@
     benchDrawLayers(*re, layers, benchState, "blurred");
 }
 
-BENCHMARK(BM_blur)->Apply(RunSkiaGLThreaded);
+BENCHMARK_CAPTURE(BM_blur, SkiaGLThreaded, RenderEngine::Threaded::YES,
+                  RenderEngine::GraphicsApi::GL);
diff --git a/libs/renderengine/include/renderengine/BorderRenderInfo.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h
deleted file mode 100644
index 0ee6661..0000000
--- a/libs/renderengine/include/renderengine/BorderRenderInfo.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 <math/mat4.h>
-#include <ui/Region.h>
-
-namespace android {
-namespace renderengine {
-
-struct BorderRenderInfo {
-    float width = 0;
-    half4 color;
-    Region combinedRegion;
-
-    bool operator==(const BorderRenderInfo& rhs) const {
-        return (width == rhs.width && color == rhs.color &&
-                combinedRegion.hasSameRects(rhs.combinedRegion));
-    }
-};
-
-} // namespace renderengine
-} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 8d7c13c..deb6253 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -22,7 +22,6 @@
 
 #include <math/mat4.h>
 #include <renderengine/PrintMatrix.h>
-#include <renderengine/BorderRenderInfo.h>
 #include <ui/DisplayId.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
@@ -87,8 +86,6 @@
     // Configures the rendering intent of the output display. This is used for tonemapping.
     aidl::android::hardware::graphics::composer3::RenderIntent renderIntent =
             aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC;
-
-    std::vector<renderengine::BorderRenderInfo> borderInfoList;
 };
 
 static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
@@ -100,8 +97,7 @@
             lhs.deviceHandlesColorTransform == rhs.deviceHandlesColorTransform &&
             lhs.orientation == rhs.orientation &&
             lhs.targetLuminanceNits == rhs.targetLuminanceNits &&
-            lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent &&
-            lhs.borderInfoList == rhs.borderInfoList;
+            lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent;
 }
 
 static const char* orientation_to_string(uint32_t orientation) {
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index b41fa1d..7207394 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -50,6 +50,11 @@
 #define PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME "debug.renderengine.capture_filename"
 
 /**
+ * Switches the cross-window background blur algorithm.
+ */
+#define PROPERTY_DEBUG_RENDERENGINE_BLUR_ALGORITHM "debug.renderengine.blur_algorithm"
+
+/**
  * Allows recording of Skia drawing commands with systrace.
  */
 #define PROPERTY_SKIA_ATRACE_ENABLED "debug.renderengine.skia_atrace_enabled"
@@ -83,6 +88,21 @@
     PROTECTED = 2,
 };
 
+// Toggles for skipping or enabling priming of particular shaders.
+struct PrimeCacheConfig {
+    bool cacheHolePunchLayer = true;
+    bool cacheSolidLayers = true;
+    bool cacheSolidDimmedLayers = true;
+    bool cacheImageLayers = true;
+    bool cacheImageDimmedLayers = true;
+    bool cacheClippedLayers = true;
+    bool cacheShadowLayers = true;
+    bool cachePIPImageLayers = true;
+    bool cacheTransparentImageDimmedLayers = true;
+    bool cacheClippedDimmedImageLayers = true;
+    bool cacheUltraHDR = true;
+};
+
 class RenderEngine {
 public:
     enum class ContextPriority {
@@ -92,22 +112,55 @@
         REALTIME = 4,
     };
 
-    enum class RenderEngineType {
-        SKIA_GL = 3,
-        SKIA_GL_THREADED = 4,
-        SKIA_VK = 5,
-        SKIA_VK_THREADED = 6,
+    enum class Threaded {
+        NO,
+        YES,
+    };
+
+    enum class GraphicsApi {
+        GL,
+        VK,
+    };
+
+    enum class SkiaBackend {
+        GANESH,
+        GRAPHITE,
+    };
+
+    enum class BlurAlgorithm {
+        NONE,
+        GAUSSIAN,
+        KAWASE,
     };
 
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
 
+    // Check if the device supports the given GraphicsApi.
+    //
+    // If called for GraphicsApi::VK then underlying (unprotected) VK resources will be preserved
+    // to optimize subsequent VK initialization, but teardown(GraphicsApi::VK) must be invoked if
+    // the caller subsequently decides to NOT use VK.
+    //
+    // The first call may require significant resource initialization, but subsequent checks are
+    // cached internally.
+    static bool canSupport(GraphicsApi graphicsApi);
+
+    // Teardown any GPU API resources that were previously initialized but are no longer needed.
+    //
+    // Must be called with GraphicsApi::VK if canSupport(GraphicsApi::VK) was previously invoked but
+    // the caller subsequently decided to not use VK.
+    //
+    // This is safe to call if there is nothing to teardown, but NOT safe to call if a RenderEngine
+    // instance exists. The RenderEngine destructor will handle its own teardown logic.
+    static void teardown(GraphicsApi graphicsApi);
+
     virtual ~RenderEngine() = 0;
 
     // ----- BEGIN DEPRECATED INTERFACE -----
     // This interface, while still in use until a suitable replacement is built,
     // should be considered deprecated, minus some methods which still may be
     // used to support legacy behavior.
-    virtual std::future<void> primeCache(bool shouldPrimeUltraHDR) = 0;
+    virtual std::future<void> primeCache(PrimeCacheConfig config) = 0;
 
     // dump the extension strings. always call the base class.
     virtual void dump(std::string& result) = 0;
@@ -176,10 +229,9 @@
     // query is required to be thread safe.
     virtual bool supportsBackgroundBlur() = 0;
 
-    // Returns the current type of RenderEngine instance that was created.
     // TODO(b/180767535): This is only implemented to allow for backend-specific behavior, which
     // we should not allow in general, so remove this.
-    RenderEngineType getRenderEngineType() const { return mRenderEngineType; }
+    bool isThreaded() const { return mThreaded == Threaded::YES; }
 
     static void validateInputBufferUsage(const sp<GraphicBuffer>&);
     static void validateOutputBufferUsage(const sp<GraphicBuffer>&);
@@ -191,9 +243,9 @@
     virtual void setEnableTracing(bool /*tracingEnabled*/) {}
 
 protected:
-    RenderEngine() : RenderEngine(RenderEngineType::SKIA_GL) {}
+    RenderEngine() : RenderEngine(Threaded::NO) {}
 
-    RenderEngine(RenderEngineType type) : mRenderEngineType(type) {}
+    RenderEngine(Threaded threaded) : mThreaded(threaded) {}
 
     // Maps GPU resources for this buffer.
     // Note that work may be deferred to an additional thread, i.e. this call
@@ -228,7 +280,7 @@
     friend class impl::ExternalTexture;
     friend class threaded::RenderEngineThreaded;
     friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test;
-    const RenderEngineType mRenderEngineType;
+    const Threaded mThreaded;
 
     // Update protectedContext mode depending on whether or not any layer has a protected buffer.
     void updateProtectedContext(const std::vector<LayerSettings>&,
@@ -249,9 +301,11 @@
     bool useColorManagement;
     bool enableProtectedContext;
     bool precacheToneMapperShaderOnly;
-    bool supportsBackgroundBlur;
+    RenderEngine::BlurAlgorithm blurAlgorithm;
     RenderEngine::ContextPriority contextPriority;
-    RenderEngine::RenderEngineType renderEngineType;
+    RenderEngine::Threaded threaded;
+    RenderEngine::GraphicsApi graphicsApi;
+    RenderEngine::SkiaBackend skiaBackend;
 
     struct Builder;
 
@@ -259,16 +313,20 @@
     // must be created by Builder via constructor with full argument list
     RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize,
                              bool _enableProtectedContext, bool _precacheToneMapperShaderOnly,
-                             bool _supportsBackgroundBlur,
+                             RenderEngine::BlurAlgorithm _blurAlgorithm,
                              RenderEngine::ContextPriority _contextPriority,
-                             RenderEngine::RenderEngineType _renderEngineType)
+                             RenderEngine::Threaded _threaded,
+                             RenderEngine::GraphicsApi _graphicsApi,
+                             RenderEngine::SkiaBackend _skiaBackend)
           : pixelFormat(_pixelFormat),
             imageCacheSize(_imageCacheSize),
             enableProtectedContext(_enableProtectedContext),
             precacheToneMapperShaderOnly(_precacheToneMapperShaderOnly),
-            supportsBackgroundBlur(_supportsBackgroundBlur),
+            blurAlgorithm(_blurAlgorithm),
             contextPriority(_contextPriority),
-            renderEngineType(_renderEngineType) {}
+            threaded(_threaded),
+            graphicsApi(_graphicsApi),
+            skiaBackend(_skiaBackend) {}
     RenderEngineCreationArgs() = delete;
 };
 
@@ -291,22 +349,30 @@
         this->precacheToneMapperShaderOnly = precacheToneMapperShaderOnly;
         return *this;
     }
-    Builder& setSupportsBackgroundBlur(bool supportsBackgroundBlur) {
-        this->supportsBackgroundBlur = supportsBackgroundBlur;
+    Builder& setBlurAlgorithm(RenderEngine::BlurAlgorithm blurAlgorithm) {
+        this->blurAlgorithm = blurAlgorithm;
         return *this;
     }
     Builder& setContextPriority(RenderEngine::ContextPriority contextPriority) {
         this->contextPriority = contextPriority;
         return *this;
     }
-    Builder& setRenderEngineType(RenderEngine::RenderEngineType renderEngineType) {
-        this->renderEngineType = renderEngineType;
+    Builder& setThreaded(RenderEngine::Threaded threaded) {
+        this->threaded = threaded;
+        return *this;
+    }
+    Builder& setGraphicsApi(RenderEngine::GraphicsApi graphicsApi) {
+        this->graphicsApi = graphicsApi;
+        return *this;
+    }
+    Builder& setSkiaBackend(RenderEngine::SkiaBackend skiaBackend) {
+        this->skiaBackend = skiaBackend;
         return *this;
     }
     RenderEngineCreationArgs build() const {
         return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext,
-                                        precacheToneMapperShaderOnly, supportsBackgroundBlur,
-                                        contextPriority, renderEngineType);
+                                        precacheToneMapperShaderOnly, blurAlgorithm,
+                                        contextPriority, threaded, graphicsApi, skiaBackend);
     }
 
 private:
@@ -315,10 +381,11 @@
     uint32_t imageCacheSize = 0;
     bool enableProtectedContext = false;
     bool precacheToneMapperShaderOnly = false;
-    bool supportsBackgroundBlur = false;
+    RenderEngine::BlurAlgorithm blurAlgorithm = RenderEngine::BlurAlgorithm::NONE;
     RenderEngine::ContextPriority contextPriority = RenderEngine::ContextPriority::MEDIUM;
-    RenderEngine::RenderEngineType renderEngineType =
-            RenderEngine::RenderEngineType::SKIA_GL_THREADED;
+    RenderEngine::Threaded threaded = RenderEngine::Threaded::YES;
+    RenderEngine::GraphicsApi graphicsApi = RenderEngine::GraphicsApi::GL;
+    RenderEngine::SkiaBackend skiaBackend = RenderEngine::SkiaBackend::GANESH;
 };
 
 } // namespace renderengine
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index a58a65c..a8c242a 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -33,7 +33,7 @@
     RenderEngine();
     ~RenderEngine() override;
 
-    MOCK_METHOD1(primeCache, std::future<void>(bool));
+    MOCK_METHOD1(primeCache, std::future<void>(PrimeCacheConfig));
     MOCK_METHOD1(dump, void(std::string&));
     MOCK_CONST_METHOD0(getMaxTextureSize, size_t());
     MOCK_CONST_METHOD0(getMaxViewportDims, size_t());
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index ee95e59..8aeef9f 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -20,81 +20,21 @@
 #define LOG_TAG "RenderEngine"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <SkImage.h>
-#include <include/gpu/ganesh/SkImageGanesh.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
-#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
-#include <include/gpu/vk/GrVkTypes.h>
-#include <android/hardware_buffer.h>
-#include "ColorSpaces.h"
-#include "log/log_main.h"
-#include "utils/Trace.h"
+#include <include/core/SkImage.h>
+#include <include/core/SkSurface.h>
+
+#include "compat/SkiaBackendTexture.h"
+
+#include <log/log_main.h>
+#include <utils/Trace.h>
 
 namespace android {
 namespace renderengine {
 namespace skia {
 
-AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer,
-                                       bool isOutputBuffer, CleanupManager& cleanupMgr)
-      : mCleanupMgr(cleanupMgr), mIsOutputBuffer(isOutputBuffer) {
-    ATRACE_CALL();
-    AHardwareBuffer_Desc desc;
-    AHardwareBuffer_describe(buffer, &desc);
-    bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
-    GrBackendFormat backendFormat;
-
-    GrBackendApi backend = context->backend();
-    if (backend == GrBackendApi::kOpenGL) {
-        backendFormat =
-                GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
-        mBackendTexture =
-                GrAHardwareBufferUtils::MakeGLBackendTexture(context,
-                                                             buffer,
-                                                             desc.width,
-                                                             desc.height,
-                                                             &mDeleteProc,
-                                                             &mUpdateProc,
-                                                             &mImageCtx,
-                                                             createProtectedImage,
-                                                             backendFormat,
-                                                             isOutputBuffer);
-    } else if (backend == GrBackendApi::kVulkan) {
-        backendFormat =
-                GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
-                                                               buffer,
-                                                               desc.format,
-                                                               false);
-        mBackendTexture =
-                GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
-                                                                 buffer,
-                                                                 desc.width,
-                                                                 desc.height,
-                                                                 &mDeleteProc,
-                                                                 &mUpdateProc,
-                                                                 &mImageCtx,
-                                                                 createProtectedImage,
-                                                                 backendFormat,
-                                                                 isOutputBuffer);
-    } else {
-        LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast<unsigned>(backend));
-    }
-
-    mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
-    if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
-        LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
-                         "isWriteable:%d format:%d",
-                         this, desc.width, desc.height, createProtectedImage, isOutputBuffer,
-                         desc.format);
-    }
-}
-
-AutoBackendTexture::~AutoBackendTexture() {
-    if (mBackendTexture.isValid()) {
-        mDeleteProc(mImageCtx);
-        mBackendTexture = {};
-    }
-}
+AutoBackendTexture::AutoBackendTexture(std::unique_ptr<SkiaBackendTexture> backendTexture,
+                                       CleanupManager& cleanupMgr)
+      : mCleanupMgr(cleanupMgr), mBackendTexture(std::move(backendTexture)) {}
 
 void AutoBackendTexture::unref(bool releaseLocalResources) {
     if (releaseLocalResources) {
@@ -122,95 +62,32 @@
     textureRelease->unref(false);
 }
 
-void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace dataspace,
-                     SkColorType colorType) {
-    switch (tex.backend()) {
-        case GrBackendApi::kOpenGL: {
-            GrGLTextureInfo textureInfo;
-            bool retrievedTextureInfo = GrBackendTextures::GetGLTextureInfo(tex, &textureInfo);
-            LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
-                             "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
-                             "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u"
-                             " colorType %i",
-                             msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
-                             tex.height(), tex.hasMipmaps(), tex.isProtected(),
-                             static_cast<int>(tex.textureType()), retrievedTextureInfo,
-                             textureInfo.fTarget, textureInfo.fFormat, colorType);
-            break;
-        }
-        case GrBackendApi::kVulkan: {
-            GrVkImageInfo imageInfo;
-            bool retrievedImageInfo = GrBackendTextures::GetVkImageInfo(tex, &imageInfo);
-            LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
-                             "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
-                             "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
-                             "fSampleCount: %u fLevelCount: %u colorType %i",
-                             msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
-                             tex.height(), tex.hasMipmaps(), tex.isProtected(),
-                             static_cast<int>(tex.textureType()), retrievedImageInfo,
-                             imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount,
-                             colorType);
-            break;
-        }
-        default:
-            LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg, static_cast<unsigned>(tex.backend()));
-            break;
-    }
-}
-
-sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
-                                             GrDirectContext* context) {
+sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) {
     ATRACE_CALL();
 
-    if (mBackendTexture.isValid()) {
-        mUpdateProc(mImageCtx, context);
-    }
-
-    auto colorType = mColorType;
-    if (alphaType == kOpaque_SkAlphaType) {
-        if (colorType == kRGBA_8888_SkColorType) {
-            colorType = kRGB_888x_SkColorType;
-        }
-    }
-
-    sk_sp<SkImage> image =
-            SkImages::BorrowTextureFrom(context, mBackendTexture, kTopLeft_GrSurfaceOrigin,
-                                        colorType, alphaType, toSkColorSpace(dataspace),
-                                        releaseImageProc, this);
-    if (image.get()) {
-        // The following ref will be counteracted by releaseProc, when SkImage is discarded.
-        ref();
-    }
+    sk_sp<SkImage> image = mBackendTexture->makeImage(alphaType, dataspace, releaseImageProc, this);
+    // The following ref will be counteracted by releaseProc, when SkImage is discarded.
+    ref();
 
     mImage = image;
     mDataspace = dataspace;
-    if (!mImage) {
-        logFatalTexture("Unable to generate SkImage.", mBackendTexture, dataspace, colorType);
-    }
     return mImage;
 }
 
-sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace,
-                                                        GrDirectContext* context) {
+sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace) {
     ATRACE_CALL();
-    LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture");
+    LOG_ALWAYS_FATAL_IF(!mBackendTexture->isOutputBuffer(),
+                        "You can't generate an SkSurface for a read-only texture");
     if (!mSurface.get() || mDataspace != dataspace) {
         sk_sp<SkSurface> surface =
-                SkSurfaces::WrapBackendTexture(context, mBackendTexture,
-                                               kTopLeft_GrSurfaceOrigin, 0, mColorType,
-                                               toSkColorSpace(dataspace), nullptr,
-                                               releaseSurfaceProc, this);
-        if (surface.get()) {
-            // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
-            ref();
-        }
+                mBackendTexture->makeSurface(dataspace, releaseSurfaceProc, this);
+        // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
+        ref();
+
         mSurface = surface;
     }
 
     mDataspace = dataspace;
-    if (!mSurface) {
-        logFatalTexture("Unable to generate SkSurface.", mBackendTexture, dataspace, mColorType);
-    }
     return mSurface;
 }
 
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index 509ac40..74daf47 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <GrAHardwareBufferUtils.h>
 #include <GrDirectContext.h>
 #include <SkImage.h>
 #include <SkSurface.h>
@@ -24,8 +23,9 @@
 #include <ui/GraphicTypes.h>
 
 #include "android-base/macros.h"
+#include "compat/SkiaBackendTexture.h"
 
-#include <mutex>
+#include <memory>
 #include <vector>
 
 namespace android {
@@ -80,9 +80,8 @@
     // of shared ownership with Skia objects, so we wrap it here instead.
     class LocalRef {
     public:
-        LocalRef(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
-                 CleanupManager& cleanupMgr) {
-            mTexture = new AutoBackendTexture(context, buffer, isOutputBuffer, cleanupMgr);
+        LocalRef(std::unique_ptr<SkiaBackendTexture> backendTexture, CleanupManager& cleanupMgr) {
+            mTexture = new AutoBackendTexture(std::move(backendTexture), cleanupMgr);
             mTexture->ref();
         }
 
@@ -95,17 +94,16 @@
         // Makes a new SkImage from the texture content.
         // As SkImages are immutable but buffer content is not, we create
         // a new SkImage every time.
-        sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
-                                 GrDirectContext* context) {
-            return mTexture->makeImage(dataspace, alphaType, context);
+        sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) {
+            return mTexture->makeImage(dataspace, alphaType);
         }
 
         // Makes a new SkSurface from the texture content, if needed.
-        sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context) {
-            return mTexture->getOrCreateSurface(dataspace, context);
+        sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace) {
+            return mTexture->getOrCreateSurface(dataspace);
         }
 
-        SkColorType colorType() const { return mTexture->mColorType; }
+        SkColorType colorType() const { return mTexture->mBackendTexture->internalColorType(); }
 
         DISALLOW_COPY_AND_ASSIGN(LocalRef);
 
@@ -114,12 +112,15 @@
     };
 
 private:
-    // Creates a GrBackendTexture whose contents come from the provided buffer.
-    AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
+    DISALLOW_COPY_AND_ASSIGN(AutoBackendTexture);
+
+    // Creates an AutoBackendTexture to manage the lifecycle of a given SkiaBackendTexture, which is
+    // in turn backed by an underlying backend-specific texture type.
+    AutoBackendTexture(std::unique_ptr<SkiaBackendTexture> backendTexture,
                        CleanupManager& cleanupMgr);
 
     // The only way to invoke dtor is with unref, when mUsageCount is 0.
-    ~AutoBackendTexture();
+    ~AutoBackendTexture() = default;
 
     void ref() { mUsageCount++; }
 
@@ -130,29 +131,21 @@
     // Makes a new SkImage from the texture content.
     // As SkImages are immutable but buffer content is not, we create
     // a new SkImage every time.
-    sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
-                             GrDirectContext* context);
+    sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType);
 
     // Makes a new SkSurface from the texture content, if needed.
-    sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context);
-
-    GrBackendTexture mBackendTexture;
-    GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
-    GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
-    GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+    sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace);
 
     CleanupManager& mCleanupMgr;
 
     static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
     static void releaseImageProc(SkImages::ReleaseContext releaseContext);
 
+    std::unique_ptr<SkiaBackendTexture> mBackendTexture;
     int mUsageCount = 0;
-
-    const bool mIsOutputBuffer;
     sk_sp<SkImage> mImage = nullptr;
     sk_sp<SkSurface> mSurface = nullptr;
     ui::Dataspace mDataspace = ui::Dataspace::UNKNOWN;
-    SkColorType mColorType = kUnknown_SkColorType;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index abe0d9b..d246870 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -630,7 +630,7 @@
 //    kFlushAfterEveryLayer = true
 // in external/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
 //    gPrintSKSL = true
-void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUltraHDR) {
+void Cache::primeShaderCache(SkiaRenderEngine* renderengine, PrimeCacheConfig config) {
     const int previousCount = renderengine->reportShadersCompiled();
     if (previousCount) {
         ALOGD("%d Shaders already compiled before Cache::primeShaderCache ran\n", previousCount);
@@ -694,13 +694,24 @@
                 impl::ExternalTexture>(srcBuffer, *renderengine,
                                        impl::ExternalTexture::Usage::READABLE |
                                                impl::ExternalTexture::Usage::WRITEABLE);
-        drawHolePunchLayer(renderengine, display, dstTexture);
-        drawSolidLayers(renderengine, display, dstTexture);
-        drawSolidLayers(renderengine, p3Display, dstTexture);
-        drawSolidDimmedLayers(renderengine, display, dstTexture);
 
-        drawShadowLayers(renderengine, display, srcTexture);
-        drawShadowLayers(renderengine, p3Display, srcTexture);
+        if (config.cacheHolePunchLayer) {
+            drawHolePunchLayer(renderengine, display, dstTexture);
+        }
+
+        if (config.cacheSolidLayers) {
+            drawSolidLayers(renderengine, display, dstTexture);
+            drawSolidLayers(renderengine, p3Display, dstTexture);
+        }
+
+        if (config.cacheSolidDimmedLayers) {
+            drawSolidDimmedLayers(renderengine, display, dstTexture);
+        }
+
+        if (config.cacheShadowLayers) {
+            drawShadowLayers(renderengine, display, srcTexture);
+            drawShadowLayers(renderengine, p3Display, srcTexture);
+        }
 
         if (renderengine->supportsBackgroundBlur()) {
             drawBlurLayers(renderengine, display, dstTexture);
@@ -737,27 +748,40 @@
         }
 
         for (auto texture : textures) {
-            drawImageLayers(renderengine, display, dstTexture, texture);
+            if (config.cacheImageLayers) {
+                drawImageLayers(renderengine, display, dstTexture, texture);
+            }
 
-            drawImageDimmedLayers(renderengine, display, dstTexture, texture);
-            drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
-            drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
+            if (config.cacheImageDimmedLayers) {
+                drawImageDimmedLayers(renderengine, display, dstTexture, texture);
+                drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
+                drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
+            }
 
-            // Draw layers for b/185569240.
-            drawClippedLayers(renderengine, display, dstTexture, texture);
+            if (config.cacheClippedLayers) {
+                // Draw layers for b/185569240.
+                drawClippedLayers(renderengine, display, dstTexture, texture);
+            }
         }
 
-        drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
+        if (config.cachePIPImageLayers) {
+            drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
+        }
 
-        drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture, externalTexture);
-        drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
-        drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture);
-        drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture,
-                                         externalTexture);
+        if (config.cacheTransparentImageDimmedLayers) {
+            drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture,
+                                             externalTexture);
+            drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
+            drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture);
+            drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture,
+                                             externalTexture);
+        }
 
-        drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+        if (config.cacheClippedDimmedImageLayers) {
+            drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+        }
 
-        if (shouldPrimeUltraHDR) {
+        if (config.cacheUltraHDR) {
             drawBT2020ClippedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
 
             drawBT2020ImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
diff --git a/libs/renderengine/skia/Cache.h b/libs/renderengine/skia/Cache.h
index 62f6705..259432f 100644
--- a/libs/renderengine/skia/Cache.h
+++ b/libs/renderengine/skia/Cache.h
@@ -16,16 +16,21 @@
 
 #pragma once
 
-namespace android::renderengine::skia {
+namespace android::renderengine {
+
+struct PrimeCacheConfig;
+
+namespace skia {
 
 class SkiaRenderEngine;
 
 class Cache {
 public:
-    static void primeShaderCache(SkiaRenderEngine*, bool shouldPrimeUltraHDR);
+    static void primeShaderCache(SkiaRenderEngine*, PrimeCacheConfig config);
 
 private:
     Cache() = default;
 };
 
-} // namespace android::renderengine::skia
+} // namespace skia
+} // namespace android::renderengine
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.cpp b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
new file mode 100644
index 0000000..68798bf
--- /dev/null
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 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 "GaneshVkRenderEngine.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
+
+#include <log/log_main.h>
+#include <sync/sync.h>
+#include <utils/Trace.h>
+
+namespace android::renderengine::skia {
+
+std::unique_ptr<GaneshVkRenderEngine> GaneshVkRenderEngine::create(
+        const RenderEngineCreationArgs& args) {
+    std::unique_ptr<GaneshVkRenderEngine> engine(new GaneshVkRenderEngine(args));
+    engine->ensureContextsCreated();
+
+    if (getVulkanInterface(false).isInitialized()) {
+        ALOGD("GaneshVkRenderEngine::%s: successfully initialized GaneshVkRenderEngine", __func__);
+        return engine;
+    } else {
+        ALOGE("GaneshVkRenderEngine::%s: could not create GaneshVkRenderEngine. "
+              "Likely insufficient Vulkan support",
+              __func__);
+        return {};
+    }
+}
+
+// Ganesh-specific function signature for fFinishedProc callback.
+static void unref_semaphore(void* semaphore) {
+    SkiaVkRenderEngine::DestroySemaphoreInfo* info =
+            reinterpret_cast<SkiaVkRenderEngine::DestroySemaphoreInfo*>(semaphore);
+    info->unref();
+}
+
+std::unique_ptr<SkiaGpuContext> GaneshVkRenderEngine::createContext(
+        VulkanInterface& vulkanInterface) {
+    return SkiaGpuContext::MakeVulkan_Ganesh(vulkanInterface.getGaneshBackendContext(),
+                                             mSkSLCacheMonitor);
+}
+
+void GaneshVkRenderEngine::waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) {
+    if (fenceFd.get() < 0) return;
+
+    const int dupedFd = dup(fenceFd.get());
+    if (dupedFd < 0) {
+        ALOGE("failed to create duplicate fence fd: %d", dupedFd);
+        sync_wait(fenceFd.get(), -1);
+        return;
+    }
+
+    base::unique_fd fenceDup(dupedFd);
+    VkSemaphore waitSemaphore =
+            getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
+    GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore);
+    constexpr bool kDeleteAfterWait = true;
+    context->grDirectContext()->wait(1, &beSemaphore, kDeleteAfterWait);
+}
+
+base::unique_fd GaneshVkRenderEngine::flushAndSubmit(SkiaGpuContext* context,
+                                                     sk_sp<SkSurface> dstSurface) {
+    sk_sp<GrDirectContext> grContext = context->grDirectContext();
+    {
+        ATRACE_NAME("flush surface");
+        // TODO: Investigate feasibility of combining this "surface flush" into the "context flush"
+        // below.
+        context->grDirectContext()->flush(dstSurface.get());
+    }
+
+    VulkanInterface& vi = getVulkanInterface(isProtected());
+    VkSemaphore semaphore = vi.createExportableSemaphore();
+    GrBackendSemaphore backendSemaphore = GrBackendSemaphores::MakeVk(semaphore);
+
+    GrFlushInfo flushInfo;
+    DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
+    if (semaphore != VK_NULL_HANDLE) {
+        destroySemaphoreInfo = new DestroySemaphoreInfo(vi, semaphore);
+        flushInfo.fNumSemaphores = 1;
+        flushInfo.fSignalSemaphores = &backendSemaphore;
+        flushInfo.fFinishedProc = unref_semaphore;
+        flushInfo.fFinishedContext = destroySemaphoreInfo;
+    }
+    GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
+    grContext->submit(GrSyncCpu::kNo);
+    int drawFenceFd = -1;
+    if (semaphore != VK_NULL_HANDLE) {
+        if (GrSemaphoresSubmitted::kYes == submitted) {
+            drawFenceFd = vi.exportSemaphoreSyncFd(semaphore);
+        }
+        // Now that drawFenceFd has been created, we can delete our reference to this semaphore
+        flushInfo.fFinishedProc(destroySemaphoreInfo);
+    }
+    base::unique_fd res(drawFenceFd);
+    return res;
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.h b/libs/renderengine/skia/GaneshVkRenderEngine.h
new file mode 100644
index 0000000..e6123c2
--- /dev/null
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 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 "skia/SkiaVkRenderEngine.h"
+
+namespace android::renderengine::skia {
+
+class GaneshVkRenderEngine : public SkiaVkRenderEngine {
+public:
+    static std::unique_ptr<GaneshVkRenderEngine> create(const RenderEngineCreationArgs& args);
+
+protected:
+    std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override;
+    void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+    base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override;
+
+private:
+    GaneshVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {}
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
new file mode 100644
index 0000000..b5cb21b
--- /dev/null
+++ b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2024 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 "GraphiteVkRenderEngine.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include <include/gpu/GpuTypes.h>
+#include <include/gpu/graphite/BackendSemaphore.h>
+#include <include/gpu/graphite/Context.h>
+#include <include/gpu/graphite/Recording.h>
+
+#include <log/log_main.h>
+#include <sync/sync.h>
+
+#include <memory>
+#include <vector>
+
+namespace android::renderengine::skia {
+
+std::unique_ptr<GraphiteVkRenderEngine> GraphiteVkRenderEngine::create(
+        const RenderEngineCreationArgs& args) {
+    std::unique_ptr<GraphiteVkRenderEngine> engine(new GraphiteVkRenderEngine(args));
+    engine->ensureContextsCreated();
+
+    if (getVulkanInterface(false).isInitialized()) {
+        ALOGD("GraphiteVkRenderEngine::%s: successfully initialized GraphiteVkRenderEngine",
+              __func__);
+        return engine;
+    } else {
+        ALOGE("GraphiteVkRenderEngine::%s: could not create GraphiteVkRenderEngine. "
+              "Likely insufficient Vulkan support",
+              __func__);
+        return {};
+    }
+}
+
+// Graphite-specific function signature for fFinishedProc callback.
+static void unref_semaphore(void* semaphore, skgpu::CallbackResult result) {
+    if (result != skgpu::CallbackResult::kSuccess) {
+        ALOGE("Graphite submission of work to GPU failed, check for Skia errors");
+    }
+    SkiaVkRenderEngine::DestroySemaphoreInfo* info =
+            reinterpret_cast<SkiaVkRenderEngine::DestroySemaphoreInfo*>(semaphore);
+    info->unref();
+}
+
+std::unique_ptr<SkiaGpuContext> GraphiteVkRenderEngine::createContext(
+        VulkanInterface& vulkanInterface) {
+    return SkiaGpuContext::MakeVulkan_Graphite(vulkanInterface.getGraphiteBackendContext());
+}
+
+void GraphiteVkRenderEngine::waitFence(SkiaGpuContext*, base::borrowed_fd fenceFd) {
+    if (fenceFd.get() < 0) return;
+
+    int dupedFd = dup(fenceFd.get());
+    if (dupedFd < 0) {
+        ALOGE("failed to create duplicate fence fd: %d", dupedFd);
+        sync_wait(fenceFd.get(), -1);
+        return;
+    }
+
+    base::unique_fd fenceDup(dupedFd);
+    VkSemaphore waitSemaphore =
+            getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
+    graphite::BackendSemaphore beSemaphore(waitSemaphore);
+    mStagedWaitSemaphores.push_back(beSemaphore);
+}
+
+base::unique_fd GraphiteVkRenderEngine::flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface>) {
+    // Minimal Recording setup. Required even if there are no incoming semaphores to wait on, and if
+    // creating the outgoing signaling semaphore fails.
+    std::unique_ptr<graphite::Recording> recording = context->graphiteRecorder()->snap();
+    graphite::InsertRecordingInfo insertInfo;
+    insertInfo.fRecording = recording.get();
+
+    VulkanInterface& vulkanInterface = getVulkanInterface(isProtected());
+    // This "signal" semaphore is called after rendering, but it is cleaned up in the same mechanism
+    // as "wait" semaphores from waitFence.
+    VkSemaphore vkSignalSemaphore = vulkanInterface.createExportableSemaphore();
+    graphite::BackendSemaphore backendSignalSemaphore(vkSignalSemaphore);
+
+    // Collect all Vk semaphores that DestroySemaphoreInfo needs to own and delete after GPU work.
+    std::vector<VkSemaphore> vkSemaphoresToCleanUp;
+    if (vkSignalSemaphore != VK_NULL_HANDLE) {
+        vkSemaphoresToCleanUp.push_back(vkSignalSemaphore);
+    }
+    for (auto backendWaitSemaphore : mStagedWaitSemaphores) {
+        vkSemaphoresToCleanUp.push_back(backendWaitSemaphore.getVkSemaphore());
+    }
+
+    DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
+    if (vkSemaphoresToCleanUp.size() > 0) {
+        destroySemaphoreInfo =
+                new DestroySemaphoreInfo(vulkanInterface, std::move(vkSemaphoresToCleanUp));
+
+        insertInfo.fNumWaitSemaphores = mStagedWaitSemaphores.size();
+        insertInfo.fWaitSemaphores = mStagedWaitSemaphores.data();
+        insertInfo.fNumSignalSemaphores = 1;
+        insertInfo.fSignalSemaphores = &backendSignalSemaphore;
+        insertInfo.fFinishedProc = unref_semaphore;
+        insertInfo.fFinishedContext = destroySemaphoreInfo;
+    }
+
+    const bool inserted = context->graphiteContext()->insertRecording(insertInfo);
+    LOG_ALWAYS_FATAL_IF(!inserted,
+                        "graphite::Context::insertRecording(...) failed, check for Skia errors");
+    const bool submitted = context->graphiteContext()->submit(graphite::SyncToCpu::kNo);
+    LOG_ALWAYS_FATAL_IF(!submitted, "graphite::Context::submit(...) failed, check for Skia errors");
+    // Skia's "backend" semaphores can be deleted immediately after inserting the recording; only
+    // the underlying VK semaphores need to be kept until GPU work is complete.
+    mStagedWaitSemaphores.clear();
+
+    base::unique_fd drawFenceFd(-1);
+    if (vkSignalSemaphore != VK_NULL_HANDLE) {
+        drawFenceFd.reset(vulkanInterface.exportSemaphoreSyncFd(vkSignalSemaphore));
+    }
+    // Now that drawFenceFd has been created, we can delete RE's reference to this semaphore, as
+    // another reference is still held until fFinishedProc is called after completion of GPU work.
+    if (destroySemaphoreInfo) {
+        destroySemaphoreInfo->unref();
+    }
+    return drawFenceFd;
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.h b/libs/renderengine/skia/GraphiteVkRenderEngine.h
new file mode 100644
index 0000000..cf24a3b
--- /dev/null
+++ b/libs/renderengine/skia/GraphiteVkRenderEngine.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 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 "SkiaVkRenderEngine.h"
+
+#include <include/gpu/graphite/BackendSemaphore.h>
+
+namespace android::renderengine::skia {
+
+class GraphiteVkRenderEngine : public SkiaVkRenderEngine {
+public:
+    static std::unique_ptr<GraphiteVkRenderEngine> create(const RenderEngineCreationArgs& args);
+
+protected:
+    std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override;
+    void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+    base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override;
+
+private:
+    GraphiteVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {}
+
+    std::vector<graphite::BackendSemaphore> mStagedWaitSemaphores;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 2053c6a..48270e1 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -21,6 +21,8 @@
 
 #include "SkiaGLRenderEngine.h"
 
+#include "compat/SkiaGpuContext.h"
+
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <GrContextOptions.h>
@@ -207,7 +209,7 @@
     std::unique_ptr<SkiaGLRenderEngine> engine(new SkiaGLRenderEngine(args, display, ctxt,
                                                                       placeholder, protectedContext,
                                                                       protectedPlaceholder));
-    engine->ensureGrContextsCreated();
+    engine->ensureContextsCreated();
 
     ALOGI("OpenGL ES informations:");
     ALOGI("vendor    : %s", extensions.getVendor());
@@ -236,7 +238,13 @@
             err = selectEGLConfig(display, format, 0, &config);
             if (err != NO_ERROR) {
                 // this EGL is too lame for android
-                LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up");
+                LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up"
+                                 " (format: %d, vendor: %s, version: %s, extensions: %s, Client"
+                                 " API: %s)",
+                                 format, eglQueryString(display, EGL_VENDOR),
+                                 eglQueryString(display, EGL_VERSION),
+                                 eglQueryString(display, EGL_EXTENSIONS),
+                                 eglQueryString(display, EGL_CLIENT_APIS) ?: "Not Supported");
             }
         }
     }
@@ -262,8 +270,8 @@
 SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                        EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
-      : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
-                         args.supportsBackgroundBlur),
+      : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat),
+                         args.blurAlgorithm),
         mEGLDisplay(display),
         mEGLContext(ctxt),
         mPlaceholderSurface(placeholder),
@@ -271,7 +279,7 @@
         mProtectedPlaceholderSurface(protectedPlaceholder) {}
 
 SkiaGLRenderEngine::~SkiaGLRenderEngine() {
-    finishRenderingAndAbandonContext();
+    finishRenderingAndAbandonContexts();
     if (mPlaceholderSurface != EGL_NO_SURFACE) {
         eglDestroySurface(mEGLDisplay, mPlaceholderSurface);
     }
@@ -289,9 +297,7 @@
     eglReleaseThread();
 }
 
-SkiaRenderEngine::Contexts SkiaGLRenderEngine::createDirectContexts(
-    const GrContextOptions& options) {
-
+SkiaRenderEngine::Contexts SkiaGLRenderEngine::createContexts() {
     LOG_ALWAYS_FATAL_IF(isProtected(),
                         "Cannot setup contexts while already in protected mode");
 
@@ -300,10 +306,10 @@
     LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed");
 
     SkiaRenderEngine::Contexts contexts;
-    contexts.first = GrDirectContexts::MakeGL(glInterface, options);
+    contexts.first = SkiaGpuContext::MakeGL_Ganesh(glInterface, mSkSLCacheMonitor);
     if (supportsProtectedContentImpl()) {
         useProtectedContextImpl(GrProtected::kYes);
-        contexts.second = GrDirectContexts::MakeGL(glInterface, options);
+        contexts.second = SkiaGpuContext::MakeGL_Ganesh(glInterface, mSkSLCacheMonitor);
         useProtectedContextImpl(GrProtected::kNo);
     }
 
@@ -324,15 +330,21 @@
     return eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE;
 }
 
-void SkiaGLRenderEngine::waitFence(GrDirectContext*, base::borrowed_fd fenceFd) {
+void SkiaGLRenderEngine::waitFence(SkiaGpuContext*, base::borrowed_fd fenceFd) {
     if (fenceFd.get() >= 0 && !waitGpuFence(fenceFd)) {
         ATRACE_NAME("SkiaGLRenderEngine::waitFence");
         sync_wait(fenceFd.get(), -1);
     }
 }
 
-base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
-    base::unique_fd drawFence = flush();
+base::unique_fd SkiaGLRenderEngine::flushAndSubmit(SkiaGpuContext* context,
+                                                   sk_sp<SkSurface> dstSurface) {
+    sk_sp<GrDirectContext> grContext = context->grDirectContext();
+    {
+        ATRACE_NAME("flush surface");
+        grContext->flush(dstSurface.get());
+    }
+    base::unique_fd drawFence = flushGL();
 
     bool requireSync = drawFence.get() < 0;
     if (requireSync) {
@@ -340,8 +352,7 @@
     } else {
         ATRACE_BEGIN("Submit(sync=false)");
     }
-    bool success = grContext->submit(requireSync ? GrSyncCpu::kYes :
-                                                   GrSyncCpu::kNo);
+    bool success = grContext->submit(requireSync ? GrSyncCpu::kYes : GrSyncCpu::kNo);
     ATRACE_END();
     if (!success) {
         ALOGE("Failed to flush RenderEngine commands");
@@ -388,7 +399,7 @@
     return true;
 }
 
-base::unique_fd SkiaGLRenderEngine::flush() {
+base::unique_fd SkiaGLRenderEngine::flushGL() {
     ATRACE_CALL();
     if (!GLExtensions::getInstance().hasNativeFenceSync()) {
         return base::unique_fd();
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index af33110..bd177e6 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -59,11 +59,11 @@
 protected:
     // Implementations of abstract SkiaRenderEngine functions specific to
     // rendering backend
-    virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+    virtual SkiaRenderEngine::Contexts createContexts();
     bool supportsProtectedContentImpl() const override;
     bool useProtectedContextImpl(GrProtected isProtected) override;
-    void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override;
-    base::unique_fd flushAndSubmit(GrDirectContext* context) override;
+    void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+    base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override;
     void appendBackendSpecificInfoToDump(std::string& result) override;
 
 private:
@@ -71,7 +71,7 @@
                        EGLSurface placeholder, EGLContext protectedContext,
                        EGLSurface protectedPlaceholder);
     bool waitGpuFence(base::borrowed_fd fenceFd);
-    base::unique_fd flush();
+    base::unique_fd flushGL();
     static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
     static EGLContext createEglContext(EGLDisplay display, EGLConfig config,
                                        EGLContext shareContext,
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 88326e7..d344164 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -53,6 +53,7 @@
 #include <SkSurface.h>
 #include <SkTileMode.h>
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
 #include <gui/FenceMonitor.h>
 #include <gui/TraceUtils.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
@@ -73,11 +74,13 @@
 
 #include "Cache.h"
 #include "ColorSpaces.h"
+#include "compat/SkiaGpuContext.h"
 #include "filters/BlurFilter.h"
 #include "filters/GaussianBlurFilter.h"
 #include "filters/KawaseBlurFilter.h"
 #include "filters/LinearEffect.h"
 #include "log/log_main.h"
+#include "skia/compat/SkiaBackendTexture.h"
 #include "skia/debug/SkiaCapture.h"
 #include "skia/debug/SkiaMemoryReporter.h"
 #include "skia/filters/StretchShaderFactory.h"
@@ -87,8 +90,7 @@
 
 // Debugging settings
 static const bool kPrintLayerSettings = false;
-static const bool kFlushAfterEveryLayer = kPrintLayerSettings;
-static constexpr bool kEnableLayerBrightening = true;
+static const bool kGaneshFlushAfterEveryLayer = kPrintLayerSettings;
 
 } // namespace
 
@@ -243,8 +245,8 @@
 
 using base::StringAppendF;
 
-std::future<void> SkiaRenderEngine::primeCache(bool shouldPrimeUltraHDR) {
-    Cache::primeShaderCache(this, shouldPrimeUltraHDR);
+std::future<void> SkiaRenderEngine::primeCache(PrimeCacheConfig config) {
+    Cache::primeShaderCache(this, config);
     return {};
 }
 
@@ -269,35 +271,48 @@
     SkAndroidFrameworkTraceUtil::setEnableTracing(tracingEnabled);
 }
 
-SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat,
-                                   bool supportsBackgroundBlur)
-      : RenderEngine(type), mDefaultPixelFormat(pixelFormat) {
-    if (supportsBackgroundBlur) {
-        ALOGD("Background Blurs Enabled");
-        mBlurFilter = new KawaseBlurFilter();
+SkiaRenderEngine::SkiaRenderEngine(Threaded threaded, PixelFormat pixelFormat,
+                                   BlurAlgorithm blurAlgorithm)
+      : RenderEngine(threaded), mDefaultPixelFormat(pixelFormat) {
+    switch (blurAlgorithm) {
+        case BlurAlgorithm::GAUSSIAN: {
+            ALOGD("Background Blurs Enabled (Gaussian algorithm)");
+            mBlurFilter = new GaussianBlurFilter();
+            break;
+        }
+        case BlurAlgorithm::KAWASE: {
+            ALOGD("Background Blurs Enabled (Kawase algorithm)");
+            mBlurFilter = new KawaseBlurFilter();
+            break;
+        }
+        default: {
+            mBlurFilter = nullptr;
+            break;
+        }
     }
+
     mCapture = std::make_unique<SkiaCapture>();
 }
 
 SkiaRenderEngine::~SkiaRenderEngine() { }
 
-// To be called from backend dtors.
-void SkiaRenderEngine::finishRenderingAndAbandonContext() {
+// To be called from backend dtors. Used to clean up Skia objects before GPU API contexts are
+// destroyed by subclasses.
+void SkiaRenderEngine::finishRenderingAndAbandonContexts() {
     std::lock_guard<std::mutex> lock(mRenderingMutex);
 
     if (mBlurFilter) {
         delete mBlurFilter;
     }
 
-    if (mGrContext) {
-        mGrContext->flushAndSubmit(GrSyncCpu::kYes);
-        mGrContext->abandonContext();
-    }
+    // Leftover textures may hold refs to backend-specific Skia contexts, which must be released
+    // before ~SkiaGpuContext is called.
+    mTextureCleanupMgr.setDeferredStatus(false);
+    mTextureCleanupMgr.cleanup();
 
-    if (mProtectedGrContext) {
-        mProtectedGrContext->flushAndSubmit(GrSyncCpu::kYes);
-        mProtectedGrContext->abandonContext();
-    }
+    // ~SkiaGpuContext must be called before GPU API contexts are torn down.
+    mContext.reset();
+    mProtectedContext.reset();
 }
 
 void SkiaRenderEngine::useProtectedContext(bool useProtectedContext) {
@@ -307,24 +322,24 @@
     }
 
     // release any scratch resources before switching into a new mode
-    if (getActiveGrContext()) {
-        getActiveGrContext()->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
+    if (getActiveContext()) {
+        getActiveContext()->purgeUnlockedScratchResources();
     }
 
     // Backend-specific way to switch to protected context
     if (useProtectedContextImpl(
             useProtectedContext ? GrProtected::kYes : GrProtected::kNo)) {
         mInProtectedContext = useProtectedContext;
-        // given that we are sharing the same thread between two GrContexts we need to
+        // given that we are sharing the same thread between two contexts we need to
         // make sure that the thread state is reset when switching between the two.
-        if (getActiveGrContext()) {
-            getActiveGrContext()->resetContext();
+        if (getActiveContext()) {
+            getActiveContext()->resetContextIfApplicable();
         }
     }
 }
 
-GrDirectContext* SkiaRenderEngine::getActiveGrContext() {
-    return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get();
+SkiaGpuContext* SkiaRenderEngine::getActiveContext() {
+    return mInProtectedContext ? mProtectedContext.get() : mContext.get();
 }
 
 static float toDegrees(uint32_t transform) {
@@ -373,26 +388,20 @@
             sourceTransfer != destTransfer;
 }
 
-void SkiaRenderEngine::ensureGrContextsCreated() {
-    if (mGrContext) {
+void SkiaRenderEngine::ensureContextsCreated() {
+    if (mContext) {
         return;
     }
 
-    GrContextOptions options;
-    options.fDisableDriverCorrectnessWorkarounds = true;
-    options.fDisableDistanceFieldPaths = true;
-    options.fReducedShaderVariations = true;
-    options.fPersistentCache = &mSkSLCacheMonitor;
-    std::tie(mGrContext, mProtectedGrContext) = createDirectContexts(options);
+    std::tie(mContext, mProtectedContext) = createContexts();
 }
 
 void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
                                                   bool isRenderable) {
     // Only run this if RE is running on its own thread. This
-    // way the access to GL operations is guaranteed to be happening on the
+    // way the access to GL/VK operations is guaranteed to be happening on the
     // same thread.
-    if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED &&
-        mRenderEngineType != RenderEngineType::SKIA_VK_THREADED) {
+    if (!isThreaded()) {
         return;
     }
     // We don't attempt to map a buffer if the buffer contains protected content. In GL this is
@@ -410,20 +419,22 @@
 
     // If we were to support caching protected buffers then we will need to switch the
     // currently bound context if we are not already using the protected context (and subsequently
-    // switch back after the buffer is cached).  However, for non-protected content we can bind
-    // the texture in either GL context because they are initialized with the same share_context
-    // which allows the texture state to be shared between them.
-    auto grContext = getActiveGrContext();
+    // switch back after the buffer is cached).
+    auto context = getActiveContext();
     auto& cache = mTextureCache;
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     mGraphicBufferExternalRefs[buffer->getId()]++;
 
     if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) {
-        std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
-                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
-                                                               buffer->toAHardwareBuffer(),
-                                                               isRenderable, mTextureCleanupMgr);
+        if (FlagManager::getInstance().renderable_buffer_usage()) {
+            isRenderable = buffer->getUsage() & GRALLOC_USAGE_HW_RENDER;
+        }
+        std::unique_ptr<SkiaBackendTexture> backendTexture =
+                context->makeBackendTexture(buffer->toAHardwareBuffer(), isRenderable);
+        auto imageTextureRef =
+                std::make_shared<AutoBackendTexture::LocalRef>(std::move(backendTexture),
+                                                               mTextureCleanupMgr);
         cache.insert({buffer->getId(), imageTextureRef});
     }
 }
@@ -474,9 +485,10 @@
             return it->second;
         }
     }
-    return std::make_shared<AutoBackendTexture::LocalRef>(getActiveGrContext(),
-                                                          buffer->toAHardwareBuffer(),
-                                                          isOutputBuffer, mTextureCleanupMgr);
+    std::unique_ptr<SkiaBackendTexture> backendTexture =
+            getActiveContext()->makeBackendTexture(buffer->toAHardwareBuffer(), isOutputBuffer);
+    return std::make_shared<AutoBackendTexture::LocalRef>(std::move(backendTexture),
+                                                          mTextureCleanupMgr);
 }
 
 bool SkiaRenderEngine::canSkipPostRenderCleanup() const {
@@ -664,9 +676,9 @@
 
     validateOutputBufferUsage(buffer->getBuffer());
 
-    auto grContext = getActiveGrContext();
-    LOG_ALWAYS_FATAL_IF(grContext->abandoned(), "GrContext is abandoned/device lost at start of %s",
-                        __func__);
+    auto context = getActiveContext();
+    LOG_ALWAYS_FATAL_IF(context->isAbandonedOrDeviceLost(),
+                        "Context is abandoned/device lost at start of %s", __func__);
 
     // any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called
     DeferTextureCleanup dtc(mTextureCleanupMgr);
@@ -674,10 +686,9 @@
     auto surfaceTextureRef = getOrCreateBackendTexture(buffer->getBuffer(), true);
 
     // wait on the buffer to be ready to use prior to using it
-    waitFence(grContext, bufferFence);
+    waitFence(context, bufferFence);
 
-    sk_sp<SkSurface> dstSurface =
-            surfaceTextureRef->getOrCreateSurface(display.outputDataspace, grContext);
+    sk_sp<SkSurface> dstSurface = surfaceTextureRef->getOrCreateSurface(display.outputDataspace);
 
     SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
     if (dstCanvas == nullptr) {
@@ -702,8 +713,7 @@
 
     // ...and compute the dimming ratio if dimming is requested
     const float displayDimmingRatio = display.targetLuminanceNits > 0.f &&
-                    maxLayerWhitePoint > 0.f &&
-                    (kEnableLayerBrightening || display.targetLuminanceNits > maxLayerWhitePoint)
+                    maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint
             ? maxLayerWhitePoint / display.targetLuminanceNits
             : 1.f;
 
@@ -761,10 +771,11 @@
             // save a snapshot of the activeSurface to use as input to the blur shaders
             blurInput = activeSurface->makeImageSnapshot();
 
-            // blit the offscreen framebuffer into the destination AHB, but only
-            // if there are blur regions. backgroundBlurRadius blurs the entire
-            // image below, so it can skip this step.
-            if (layer.blurRegions.size()) {
+            // blit the offscreen framebuffer into the destination AHB. This ensures that
+            // even if the blurred image does not cover the screen (for example, during
+            // a rotation animation, or if blur regions are used), the entire screen is
+            // initialized.
+            if (layer.blurRegions.size() || FlagManager::getInstance().restore_blur_step()) {
                 SkPaint paint;
                 paint.setBlendMode(SkBlendMode::kSrc);
                 if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
@@ -841,7 +852,7 @@
             if (blurRect.width() > 0 && blurRect.height() > 0) {
                 if (layer.backgroundBlurRadius > 0) {
                     ATRACE_NAME("BackgroundBlur");
-                    auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius,
+                    auto blurredImage = mBlurFilter->generate(context, layer.backgroundBlurRadius,
                                                               blurInput, blurRect);
 
                     cachedBlurs[layer.backgroundBlurRadius] = blurredImage;
@@ -855,7 +866,7 @@
                     if (cachedBlurs[region.blurRadius] == nullptr) {
                         ATRACE_NAME("BlurRegion");
                         cachedBlurs[region.blurRadius] =
-                                mBlurFilter->generate(grContext, region.blurRadius, blurInput,
+                                mBlurFilter->generate(context, region.blurRadius, blurInput,
                                                       blurRect);
                     }
 
@@ -944,7 +955,7 @@
             // if the layer's buffer has a fence, then we must must respect the fence prior to using
             // the buffer.
             if (layer.source.buffer.fence != nullptr) {
-                waitFence(grContext, layer.source.buffer.fence->get());
+                waitFence(context, layer.source.buffer.fence->get());
             }
 
             // isOpaque means we need to ignore the alpha in the image,
@@ -968,7 +979,7 @@
                     : item.isOpaque                      ? kOpaque_SkAlphaType
                     : item.usePremultipliedAlpha         ? kPremul_SkAlphaType
                                                          : kUnpremul_SkAlphaType;
-            sk_sp<SkImage> image = imageTextureRef->makeImage(layerDataspace, alphaType, grContext);
+            sk_sp<SkImage> image = imageTextureRef->makeImage(layerDataspace, alphaType);
 
             auto texMatrix = getSkM44(item.textureTransform).asM33();
             // textureTansform was intended to be passed directly into a shader, so when
@@ -1122,40 +1133,21 @@
         } else {
             canvas->drawRect(bounds.rect(), paint);
         }
-        if (kFlushAfterEveryLayer) {
+        if (kGaneshFlushAfterEveryLayer) {
             ATRACE_NAME("flush surface");
+            // No-op in Graphite. If "flushing" Skia's drawing commands after each layer is desired
+            // in Graphite, then a graphite::Recording would need to be snapped and tracked for each
+            // layer, which is likely possible but adds non-trivial complexity (in both bookkeeping
+            // and refactoring).
             skgpu::ganesh::Flush(activeSurface);
         }
     }
-    for (const auto& borderRenderInfo : display.borderInfoList) {
-        SkPaint p;
-        p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g,
-                             borderRenderInfo.color.b, borderRenderInfo.color.a});
-        p.setAntiAlias(true);
-        p.setStyle(SkPaint::kStroke_Style);
-        p.setStrokeWidth(borderRenderInfo.width);
-        SkRegion sk_region;
-        SkPath path;
-
-        // Construct a final SkRegion using Regions
-        for (const auto& r : borderRenderInfo.combinedRegion) {
-            sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op);
-        }
-
-        sk_region.getBoundaryPath(&path);
-        canvas->drawPath(path, p);
-        path.close();
-    }
 
     surfaceAutoSaveRestore.restore();
     mCapture->endCapture();
-    {
-        ATRACE_NAME("flush surface");
-        LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
-        skgpu::ganesh::Flush(activeSurface);
-    }
 
-    auto drawFence = sp<Fence>::make(flushAndSubmit(grContext));
+    LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
+    auto drawFence = sp<Fence>::make(flushAndSubmit(context, dstSurface));
 
     if (ATRACE_ENABLED()) {
         static gui::FenceMonitor sMonitor("RE Completion");
@@ -1165,11 +1157,11 @@
 }
 
 size_t SkiaRenderEngine::getMaxTextureSize() const {
-    return mGrContext->maxTextureSize();
+    return mContext->getMaxTextureSize();
 }
 
 size_t SkiaRenderEngine::getMaxViewportDims() const {
-    return mGrContext->maxRenderTargetSize();
+    return mContext->getMaxRenderTargetSize();
 }
 
 void SkiaRenderEngine::drawShadow(SkCanvas* canvas,
@@ -1195,13 +1187,13 @@
     const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER;
 
     // start by resizing the current context
-    getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
+    getActiveContext()->setResourceCacheLimit(maxResourceBytes);
 
     // if it is possible to switch contexts then we will resize the other context
     const bool originalProtectedState = mInProtectedContext;
     useProtectedContext(!mInProtectedContext);
     if (mInProtectedContext != originalProtectedState) {
-        getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
+        getActiveContext()->setResourceCacheLimit(maxResourceBytes);
         // reset back to the initial context that was active when this method was called
         useProtectedContext(originalProtectedState);
     }
@@ -1241,7 +1233,7 @@
                 {"skia", "Other"},
         };
         SkiaMemoryReporter gpuReporter(gpuResourceMap, true);
-        mGrContext->dumpMemoryStatistics(&gpuReporter);
+        mContext->dumpMemoryStatistics(&gpuReporter);
         StringAppendF(&result, "Skia's GPU Caches: ");
         gpuReporter.logTotals(result);
         gpuReporter.logOutput(result);
@@ -1265,8 +1257,8 @@
         StringAppendF(&result, "\n");
 
         SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true);
-        if (mProtectedGrContext) {
-            mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter);
+        if (mProtectedContext) {
+            mProtectedContext->dumpMemoryStatistics(&gpuProtectedReporter);
         }
         StringAppendF(&result, "Skia's GPU Protected Caches: ");
         gpuProtectedReporter.logTotals(result);
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index ac134af..c8f9241 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -21,21 +21,21 @@
 #include <sys/types.h>
 
 #include <GrBackendSemaphore.h>
-#include <GrDirectContext.h>
 #include <SkSurface.h>
 #include <android-base/thread_annotations.h>
 #include <renderengine/ExternalTexture.h>
 #include <renderengine/RenderEngine.h>
 #include <sys/types.h>
 
+#include <memory>
 #include <mutex>
 #include <unordered_map>
 
 #include "AutoBackendTexture.h"
 #include "GrContextOptions.h"
 #include "SkImageInfo.h"
-#include "SkiaRenderEngine.h"
 #include "android-base/macros.h"
+#include "compat/SkiaGpuContext.h"
 #include "debug/SkiaCapture.h"
 #include "filters/BlurFilter.h"
 #include "filters/LinearEffect.h"
@@ -59,10 +59,10 @@
 class SkiaRenderEngine : public RenderEngine {
 public:
     static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, bool supportsBackgroundBlur);
+    SkiaRenderEngine(Threaded, PixelFormat pixelFormat, BlurAlgorithm);
     ~SkiaRenderEngine() override;
 
-    std::future<void> primeCache(bool shouldPrimeUltraHDR) override final;
+    std::future<void> primeCache(PrimeCacheConfig config) override final;
     void cleanupPostRender() override final;
     bool supportsBackgroundBlur() override final {
         return mBlurFilter != nullptr;
@@ -76,24 +76,27 @@
     bool supportsProtectedContent() const override {
         return supportsProtectedContentImpl();
     }
-    void ensureGrContextsCreated();
+    void ensureContextsCreated();
+
 protected:
-    // This is so backends can stop the generic rendering state first before
-    // cleaning up backend-specific state
-    void finishRenderingAndAbandonContext();
+    // This is so backends can stop the generic rendering state first before cleaning up
+    // backend-specific state. SkiaGpuContexts are invalid after invocation.
+    void finishRenderingAndAbandonContexts();
 
     // Functions that a given backend (GLES, Vulkan) must implement
-    using Contexts = std::pair<sk_sp<GrDirectContext>, sk_sp<GrDirectContext>>;
-    virtual Contexts createDirectContexts(const GrContextOptions& options) = 0;
+    using Contexts = std::pair<unique_ptr<SkiaGpuContext>, unique_ptr<SkiaGpuContext>>;
+    virtual Contexts createContexts() = 0;
     virtual bool supportsProtectedContentImpl() const = 0;
     virtual bool useProtectedContextImpl(GrProtected isProtected) = 0;
-    virtual void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) = 0;
-    virtual base::unique_fd flushAndSubmit(GrDirectContext* context) = 0;
+    virtual void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) = 0;
+    virtual base::unique_fd flushAndSubmit(SkiaGpuContext* context,
+                                           sk_sp<SkSurface> dstSurface) = 0;
     virtual void appendBackendSpecificInfoToDump(std::string& result) = 0;
 
     size_t getMaxTextureSize() const override final;
     size_t getMaxViewportDims() const override final;
-    GrDirectContext* getActiveGrContext();
+    // TODO: b/293371537 - Return reference instead of pointer? (Cleanup)
+    SkiaGpuContext* getActiveContext();
 
     bool isProtected() const { return mInProtectedContext; }
 
@@ -121,6 +124,8 @@
         int mTotalShadersCompiled = 0;
     };
 
+    SkSLCacheMonitor mSkSLCacheMonitor;
+
 private:
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
                                   bool isRenderable) override final;
@@ -163,9 +168,6 @@
     // Number of external holders of ExternalTexture references, per GraphicBuffer ID.
     std::unordered_map<GraphicBufferId, int32_t> mGraphicBufferExternalRefs
             GUARDED_BY(mRenderingMutex);
-    // For GL, this cache is shared between protected and unprotected contexts. For Vulkan, it is
-    // only used for the unprotected context, because Vulkan does not allow sharing between
-    // contexts, and protected is less common.
     std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
     std::unordered_map<shaders::LinearEffect, sk_sp<SkRuntimeEffect>, shaders::LinearEffectHasher>
@@ -183,12 +185,11 @@
     // Mutex guarding rendering operations, so that internal state related to
     // rendering that is potentially modified by multiple threads is guaranteed thread-safe.
     mutable std::mutex mRenderingMutex;
-    SkSLCacheMonitor mSkSLCacheMonitor;
 
     // Graphics context used for creating surfaces and submitting commands
-    sk_sp<GrDirectContext> mGrContext;
+    unique_ptr<SkiaGpuContext> mContext;
     // Same as above, but for protected content (eg. DRM)
-    sk_sp<GrDirectContext> mProtectedGrContext;
+    unique_ptr<SkiaGpuContext> mProtectedContext;
     bool mInProtectedContext = false;
 };
 
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index ba20d1f..bd50107 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -21,20 +21,24 @@
 
 #include "SkiaVkRenderEngine.h"
 
+#include "GaneshVkRenderEngine.h"
+#include "compat/SkiaGpuContext.h"
+
 #include <GrBackendSemaphore.h>
 #include <GrContextOptions.h>
+#include <GrDirectContext.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
-#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 
 #include <android-base/stringprintf.h>
 #include <gui/TraceUtils.h>
 #include <sync/sync.h>
 #include <utils/Trace.h>
 
-#include <cstdint>
 #include <memory>
-#include <vector>
+#include <string>
 
 #include <vulkan/vulkan.h>
 #include "log/log_main.h"
@@ -42,667 +46,124 @@
 namespace android {
 namespace renderengine {
 
-struct VulkanFuncs {
-    PFN_vkCreateSemaphore vkCreateSemaphore = nullptr;
-    PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr;
-    PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr;
-    PFN_vkDestroySemaphore vkDestroySemaphore = nullptr;
-
-    PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr;
-    PFN_vkDestroyDevice vkDestroyDevice = nullptr;
-    PFN_vkDestroyInstance vkDestroyInstance = nullptr;
-};
-
-// Ref-Count a semaphore
-struct DestroySemaphoreInfo {
-    VkSemaphore mSemaphore;
-    // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia
-    // (including by the GPU) and inside SkiaVkRenderEngine. So we always start with two refs, one
-    // owned by Skia and one owned by the SkiaVkRenderEngine. The refs are decremented each time
-    // delete_semaphore* is called with this object. Skia will call destroy_semaphore* once it is
-    // done with the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine
-    // calls delete_semaphore* after sending the semaphore to Skia and exporting it if need be.
-    int mRefs = 2;
-
-    DestroySemaphoreInfo(VkSemaphore semaphore) : mSemaphore(semaphore) {}
-};
-
-struct VulkanInterface {
-    bool initialized = false;
-    VkInstance instance;
-    VkPhysicalDevice physicalDevice;
-    VkDevice device;
-    VkQueue queue;
-    int queueIndex;
-    uint32_t apiVersion;
-    GrVkExtensions grExtensions;
-    VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr;
-    VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr;
-    VkPhysicalDeviceProtectedMemoryFeatures* protectedMemoryFeatures = nullptr;
-    GrVkGetProc grGetProc;
-    bool isProtected;
-    bool isRealtimePriority;
-
-    VulkanFuncs funcs;
-
-    std::vector<std::string> instanceExtensionNames;
-    std::vector<std::string> deviceExtensionNames;
-
-    GrVkBackendContext getBackendContext() {
-        GrVkBackendContext backendContext;
-        backendContext.fInstance = instance;
-        backendContext.fPhysicalDevice = physicalDevice;
-        backendContext.fDevice = device;
-        backendContext.fQueue = queue;
-        backendContext.fGraphicsQueueIndex = queueIndex;
-        backendContext.fMaxAPIVersion = apiVersion;
-        backendContext.fVkExtensions = &grExtensions;
-        backendContext.fDeviceFeatures2 = physicalDeviceFeatures2;
-        backendContext.fGetProc = grGetProc;
-        backendContext.fProtectedContext = isProtected ? GrProtected::kYes : GrProtected::kNo;
-        return backendContext;
-    };
-
-    VkSemaphore createExportableSemaphore() {
-        VkExportSemaphoreCreateInfo exportInfo;
-        exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
-        exportInfo.pNext = nullptr;
-        exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
-        VkSemaphoreCreateInfo semaphoreInfo;
-        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
-        semaphoreInfo.pNext = &exportInfo;
-        semaphoreInfo.flags = 0;
-
-        VkSemaphore semaphore;
-        VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
-        if (VK_SUCCESS != err) {
-            ALOGE("%s: failed to create semaphore. err %d\n", __func__, err);
-            return VK_NULL_HANDLE;
-        }
-
-        return semaphore;
-    }
-
-    // syncFd cannot be <= 0
-    VkSemaphore importSemaphoreFromSyncFd(int syncFd) {
-        VkSemaphoreCreateInfo semaphoreInfo;
-        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
-        semaphoreInfo.pNext = nullptr;
-        semaphoreInfo.flags = 0;
-
-        VkSemaphore semaphore;
-        VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
-        if (VK_SUCCESS != err) {
-            ALOGE("%s: failed to create import semaphore", __func__);
-            return VK_NULL_HANDLE;
-        }
-
-        VkImportSemaphoreFdInfoKHR importInfo;
-        importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
-        importInfo.pNext = nullptr;
-        importInfo.semaphore = semaphore;
-        importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;
-        importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-        importInfo.fd = syncFd;
-
-        err = funcs.vkImportSemaphoreFdKHR(device, &importInfo);
-        if (VK_SUCCESS != err) {
-            funcs.vkDestroySemaphore(device, semaphore, nullptr);
-            ALOGE("%s: failed to import semaphore", __func__);
-            return VK_NULL_HANDLE;
-        }
-
-        return semaphore;
-    }
-
-    int exportSemaphoreSyncFd(VkSemaphore semaphore) {
-        int res;
-
-        VkSemaphoreGetFdInfoKHR getFdInfo;
-        getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
-        getFdInfo.pNext = nullptr;
-        getFdInfo.semaphore = semaphore;
-        getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
-        VkResult err = funcs.vkGetSemaphoreFdKHR(device, &getFdInfo, &res);
-        if (VK_SUCCESS != err) {
-            ALOGE("%s: failed to export semaphore, err: %d", __func__, err);
-            return -1;
-        }
-        return res;
-    }
-
-    void destroySemaphore(VkSemaphore semaphore) {
-        funcs.vkDestroySemaphore(device, semaphore, nullptr);
-    }
-};
-
-static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
-    if (device != VK_NULL_HANDLE) {
-        return vkGetDeviceProcAddr(device, proc_name);
-    }
-    return vkGetInstanceProcAddr(instance, proc_name);
-};
-
-#define BAIL(fmt, ...)                                          \
-    {                                                           \
-        ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \
-        return interface;                                       \
-    }
-
-#define CHECK_NONNULL(expr)       \
-    if ((expr) == nullptr) {      \
-        BAIL("[%s] null", #expr); \
-    }
-
-#define VK_CHECK(expr)                              \
-    if ((expr) != VK_SUCCESS) {                     \
-        BAIL("[%s] failed. err = %d", #expr, expr); \
-        return interface;                           \
-    }
-
-#define VK_GET_PROC(F)                                                           \
-    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \
-    CHECK_NONNULL(vk##F)
-#define VK_GET_INST_PROC(instance, F)                                      \
-    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \
-    CHECK_NONNULL(vk##F)
-#define VK_GET_DEV_PROC(device, F)                                     \
-    PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \
-    CHECK_NONNULL(vk##F)
-
-VulkanInterface initVulkanInterface(bool protectedContent = false) {
-    VulkanInterface interface;
-
-    VK_GET_PROC(EnumerateInstanceVersion);
-    uint32_t instanceVersion;
-    VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion));
-
-    if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
-        return interface;
-    }
-
-    const VkApplicationInfo appInfo = {
-            VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0,
-            VK_MAKE_VERSION(1, 1, 0),
-    };
-
-    VK_GET_PROC(EnumerateInstanceExtensionProperties);
-
-    uint32_t extensionCount = 0;
-    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
-    std::vector<VkExtensionProperties> instanceExtensions(extensionCount);
-    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
-                                                    instanceExtensions.data()));
-    std::vector<const char*> enabledInstanceExtensionNames;
-    enabledInstanceExtensionNames.reserve(instanceExtensions.size());
-    interface.instanceExtensionNames.reserve(instanceExtensions.size());
-    for (const auto& instExt : instanceExtensions) {
-        enabledInstanceExtensionNames.push_back(instExt.extensionName);
-        interface.instanceExtensionNames.push_back(instExt.extensionName);
-    }
-
-    const VkInstanceCreateInfo instanceCreateInfo = {
-            VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
-            nullptr,
-            0,
-            &appInfo,
-            0,
-            nullptr,
-            (uint32_t)enabledInstanceExtensionNames.size(),
-            enabledInstanceExtensionNames.data(),
-    };
-
-    VK_GET_PROC(CreateInstance);
-    VkInstance instance;
-    VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
-
-    VK_GET_INST_PROC(instance, DestroyInstance);
-    interface.funcs.vkDestroyInstance = vkDestroyInstance;
-    VK_GET_INST_PROC(instance, EnumeratePhysicalDevices);
-    VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties);
-    VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2);
-    VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties);
-    VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2);
-    VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2);
-    VK_GET_INST_PROC(instance, CreateDevice);
-
-    uint32_t physdevCount;
-    VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr));
-    if (physdevCount == 0) {
-        BAIL("Could not find any physical devices");
-    }
-
-    physdevCount = 1;
-    VkPhysicalDevice physicalDevice;
-    VkResult enumeratePhysDevsErr =
-            vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice);
-    if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) {
-        BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d",
-             enumeratePhysDevsErr);
-    }
-
-    VkPhysicalDeviceProperties2 physDevProps = {
-            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
-            0,
-            {},
-    };
-    VkPhysicalDeviceProtectedMemoryProperties protMemProps = {
-            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
-            0,
-            {},
-    };
-
-    if (protectedContent) {
-        physDevProps.pNext = &protMemProps;
-    }
-
-    vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
-    if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) {
-        BAIL("Could not find a Vulkan 1.1+ physical device");
-    }
-
-    // Check for syncfd support. Bail if we cannot both import and export them.
-    VkPhysicalDeviceExternalSemaphoreInfo semInfo = {
-            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
-            nullptr,
-            VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-    };
-    VkExternalSemaphoreProperties semProps = {
-            VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0,
-    };
-    vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps);
-
-    bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes &
-                                             VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
-            (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
-            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) &&
-            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
-
-    if (!sufficientSemaphoreSyncFdSupport) {
-        BAIL("Vulkan device does not support sufficient external semaphore sync fd features. "
-             "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
-             "compatibleHandleTypes 0x%x (needed 0x%x) "
-             "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
-             semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-             semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-             semProps.externalSemaphoreFeatures,
-             VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
-                     VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
-    } else {
-        ALOGD("Vulkan device supports sufficient external semaphore sync fd features. "
-              "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
-              "compatibleHandleTypes 0x%x (needed 0x%x) "
-              "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
-              semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-              semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-              semProps.externalSemaphoreFeatures,
-              VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
-                      VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
-    }
-
-    uint32_t queueCount;
-    vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr);
-    if (queueCount == 0) {
-        BAIL("Could not find queues for physical device");
-    }
-
-    std::vector<VkQueueFamilyProperties2> queueProps(queueCount);
-    std::vector<VkQueueFamilyGlobalPriorityPropertiesEXT> queuePriorityProps(queueCount);
-    VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
-    // Even though we don't yet know if the VK_EXT_global_priority extension is available,
-    // we can safely add the request to the pNext chain, and if the extension is not
-    // available, it will be ignored.
-    for (uint32_t i = 0; i < queueCount; ++i) {
-        queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
-        queuePriorityProps[i].pNext = nullptr;
-        queueProps[i].pNext = &queuePriorityProps[i];
-    }
-    vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data());
-
-    int graphicsQueueIndex = -1;
-    for (uint32_t i = 0; i < queueCount; ++i) {
-        // Look at potential answers to the VK_EXT_global_priority query.  If answers were
-        // provided, we may adjust the queuePriority.
-        if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
-            for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) {
-                if (queuePriorityProps[i].priorities[j] > queuePriority) {
-                    queuePriority = queuePriorityProps[i].priorities[j];
-                }
-            }
-            if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) {
-                interface.isRealtimePriority = true;
-            }
-            graphicsQueueIndex = i;
-            break;
-        }
-    }
-
-    if (graphicsQueueIndex == -1) {
-        BAIL("Could not find a graphics queue family");
-    }
-
-    uint32_t deviceExtensionCount;
-    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
-                                                  nullptr));
-    std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
-    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
-                                                  deviceExtensions.data()));
-
-    std::vector<const char*> enabledDeviceExtensionNames;
-    enabledDeviceExtensionNames.reserve(deviceExtensions.size());
-    interface.deviceExtensionNames.reserve(deviceExtensions.size());
-    for (const auto& devExt : deviceExtensions) {
-        enabledDeviceExtensionNames.push_back(devExt.extensionName);
-        interface.deviceExtensionNames.push_back(devExt.extensionName);
-    }
-
-    interface.grExtensions.init(sGetProc, instance, physicalDevice,
-                                enabledInstanceExtensionNames.size(),
-                                enabledInstanceExtensionNames.data(),
-                                enabledDeviceExtensionNames.size(),
-                                enabledDeviceExtensionNames.data());
-
-    if (!interface.grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
-        BAIL("Vulkan driver doesn't support external semaphore fd");
-    }
-
-    interface.physicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2;
-    interface.physicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
-    interface.physicalDeviceFeatures2->pNext = nullptr;
-
-    interface.samplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures;
-    interface.samplerYcbcrConversionFeatures->sType =
-            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
-    interface.samplerYcbcrConversionFeatures->pNext = nullptr;
-
-    interface.physicalDeviceFeatures2->pNext = interface.samplerYcbcrConversionFeatures;
-    void** tailPnext = &interface.samplerYcbcrConversionFeatures->pNext;
-
-    if (protectedContent) {
-        interface.protectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures;
-        interface.protectedMemoryFeatures->sType =
-                VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
-        interface.protectedMemoryFeatures->pNext = nullptr;
-        *tailPnext = interface.protectedMemoryFeatures;
-        tailPnext = &interface.protectedMemoryFeatures->pNext;
-    }
-
-    vkGetPhysicalDeviceFeatures2(physicalDevice, interface.physicalDeviceFeatures2);
-    // Looks like this would slow things down and we can't depend on it on all platforms
-    interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
-
-    if (protectedContent && !interface.protectedMemoryFeatures->protectedMemory) {
-        BAIL("Protected memory not supported");
-    }
-
-    float queuePriorities[1] = {0.0f};
-    void* queueNextPtr = nullptr;
-
-    VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = {
-            VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT,
-            nullptr,
-            // If queue priority is supported, RE should always have realtime priority.
-            queuePriority,
-    };
-
-    if (interface.grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
-        queueNextPtr = &queuePriorityCreateInfo;
-    }
-
-    VkDeviceQueueCreateFlags deviceQueueCreateFlags =
-            (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0);
-
-    const VkDeviceQueueCreateInfo queueInfo = {
-            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
-            queueNextPtr,
-            deviceQueueCreateFlags,
-            (uint32_t)graphicsQueueIndex,
-            1,
-            queuePriorities,
-    };
-
-    const VkDeviceCreateInfo deviceInfo = {
-            VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
-            interface.physicalDeviceFeatures2,
-            0,
-            1,
-            &queueInfo,
-            0,
-            nullptr,
-            (uint32_t)enabledDeviceExtensionNames.size(),
-            enabledDeviceExtensionNames.data(),
-            nullptr,
-    };
-
-    ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent);
-    VkDevice device;
-    VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device));
-    ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
-
-    VkQueue graphicsQueue;
-    VK_GET_DEV_PROC(device, GetDeviceQueue2);
-    const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr,
-                                                 deviceQueueCreateFlags,
-                                                 (uint32_t)graphicsQueueIndex, 0};
-    vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue);
-
-    VK_GET_DEV_PROC(device, DeviceWaitIdle);
-    VK_GET_DEV_PROC(device, DestroyDevice);
-    interface.funcs.vkDeviceWaitIdle = vkDeviceWaitIdle;
-    interface.funcs.vkDestroyDevice = vkDestroyDevice;
-
-    VK_GET_DEV_PROC(device, CreateSemaphore);
-    VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR);
-    VK_GET_DEV_PROC(device, GetSemaphoreFdKHR);
-    VK_GET_DEV_PROC(device, DestroySemaphore);
-    interface.funcs.vkCreateSemaphore = vkCreateSemaphore;
-    interface.funcs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR;
-    interface.funcs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR;
-    interface.funcs.vkDestroySemaphore = vkDestroySemaphore;
-
-    // At this point, everything's succeeded and we can continue
-    interface.initialized = true;
-    interface.instance = instance;
-    interface.physicalDevice = physicalDevice;
-    interface.device = device;
-    interface.queue = graphicsQueue;
-    interface.queueIndex = graphicsQueueIndex;
-    interface.apiVersion = physDevProps.properties.apiVersion;
-    // grExtensions already constructed
-    // feature pointers already constructed
-    interface.grGetProc = sGetProc;
-    interface.isProtected = protectedContent;
-    // funcs already initialized
-
-    ALOGD("%s: Success init Vulkan interface", __func__);
-    return interface;
-}
-
-void teardownVulkanInterface(VulkanInterface* interface) {
-    interface->initialized = false;
-
-    if (interface->device != VK_NULL_HANDLE) {
-        interface->funcs.vkDeviceWaitIdle(interface->device);
-        interface->funcs.vkDestroyDevice(interface->device, nullptr);
-        interface->device = VK_NULL_HANDLE;
-    }
-    if (interface->instance != VK_NULL_HANDLE) {
-        interface->funcs.vkDestroyInstance(interface->instance, nullptr);
-        interface->instance = VK_NULL_HANDLE;
-    }
-
-    if (interface->protectedMemoryFeatures) {
-        delete interface->protectedMemoryFeatures;
-    }
-
-    if (interface->samplerYcbcrConversionFeatures) {
-        delete interface->samplerYcbcrConversionFeatures;
-    }
-
-    if (interface->physicalDeviceFeatures2) {
-        delete interface->physicalDeviceFeatures2;
-    }
-
-    interface->samplerYcbcrConversionFeatures = nullptr;
-    interface->physicalDeviceFeatures2 = nullptr;
-    interface->protectedMemoryFeatures = nullptr;
-}
-
-static VulkanInterface sVulkanInterface;
-static VulkanInterface sProtectedContentVulkanInterface;
+static skia::VulkanInterface sVulkanInterface;
+static skia::VulkanInterface sProtectedContentVulkanInterface;
 
 static void sSetupVulkanInterface() {
-    if (!sVulkanInterface.initialized) {
-        sVulkanInterface = initVulkanInterface(false /* no protected content */);
+    if (!sVulkanInterface.isInitialized()) {
+        sVulkanInterface.init(false /* no protected content */);
         // We will have to abort if non-protected VkDevice creation fails (then nothing works).
-        LOG_ALWAYS_FATAL_IF(!sVulkanInterface.initialized,
+        LOG_ALWAYS_FATAL_IF(!sVulkanInterface.isInitialized(),
                             "Could not initialize Vulkan RenderEngine!");
     }
-    if (!sProtectedContentVulkanInterface.initialized) {
-        sProtectedContentVulkanInterface = initVulkanInterface(true /* protected content */);
-        if (!sProtectedContentVulkanInterface.initialized) {
+    if (!sProtectedContentVulkanInterface.isInitialized()) {
+        sProtectedContentVulkanInterface.init(true /* protected content */);
+        if (!sProtectedContentVulkanInterface.isInitialized()) {
             ALOGE("Could not initialize protected content Vulkan RenderEngine.");
         }
     }
 }
 
+bool RenderEngine::canSupport(GraphicsApi graphicsApi) {
+    switch (graphicsApi) {
+        case GraphicsApi::GL:
+            return true;
+        case GraphicsApi::VK: {
+            // Static local variables are initialized once, on first invocation of the function.
+            static const bool canSupportVulkan = []() {
+                if (!sVulkanInterface.isInitialized()) {
+                    sVulkanInterface.init(false /* no protected content */);
+                    ALOGD("%s: initialized == %s.", __func__,
+                          sVulkanInterface.isInitialized() ? "true" : "false");
+                    if (!sVulkanInterface.isInitialized()) {
+                        sVulkanInterface.teardown();
+                        return false;
+                    }
+                }
+                return true;
+            }();
+            return canSupportVulkan;
+        }
+    }
+}
+
+void RenderEngine::teardown(GraphicsApi graphicsApi) {
+    switch (graphicsApi) {
+        case GraphicsApi::GL:
+            break;
+        case GraphicsApi::VK: {
+            if (sVulkanInterface.isInitialized()) {
+                sVulkanInterface.teardown();
+                ALOGD("Tearing down the unprotected VulkanInterface.");
+            }
+            if (sProtectedContentVulkanInterface.isInitialized()) {
+                sProtectedContentVulkanInterface.teardown();
+                ALOGD("Tearing down the protected VulkanInterface.");
+            }
+            break;
+        }
+    }
+}
+
 namespace skia {
 
 using base::StringAppendF;
 
-bool SkiaVkRenderEngine::canSupportSkiaVkRenderEngine() {
-    VulkanInterface temp = initVulkanInterface(false /* no protected content */);
-    ALOGD("SkiaVkRenderEngine::canSupportSkiaVkRenderEngine(): initialized == %s.",
-          temp.initialized ? "true" : "false");
-    return temp.initialized;
-}
-
-std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create(
-        const RenderEngineCreationArgs& args) {
-    std::unique_ptr<SkiaVkRenderEngine> engine(new SkiaVkRenderEngine(args));
-    engine->ensureGrContextsCreated();
-
-    if (sVulkanInterface.initialized) {
-        ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__);
-        return engine;
-    } else {
-        ALOGD("SkiaVkRenderEngine::%s: could not create SkiaVkRenderEngine. "
-              "Likely insufficient Vulkan support",
-              __func__);
-        return {};
-    }
-}
-
 SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args)
-      : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
-                         args.supportsBackgroundBlur) {}
+      : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat),
+                         args.blurAlgorithm) {}
 
 SkiaVkRenderEngine::~SkiaVkRenderEngine() {
-    finishRenderingAndAbandonContext();
+    finishRenderingAndAbandonContexts();
+    // Teardown VulkanInterfaces after Skia contexts have been abandoned
+    teardown(GraphicsApi::VK);
 }
 
-SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts(
-        const GrContextOptions& options) {
+SkiaRenderEngine::Contexts SkiaVkRenderEngine::createContexts() {
     sSetupVulkanInterface();
+    // More work would need to be done in order to have multiple RenderEngine instances. In
+    // particular, they would not be able to share the same VulkanInterface(s).
+    LOG_ALWAYS_FATAL_IF(!sVulkanInterface.takeOwnership(),
+                        "SkiaVkRenderEngine couldn't take ownership of existing unprotected "
+                        "VulkanInterface! Only one SkiaVkRenderEngine instance may exist at a "
+                        "time.");
+    if (sProtectedContentVulkanInterface.isInitialized()) {
+        // takeOwnership fails on an uninitialized VulkanInterface, but protected content support is
+        // optional.
+        LOG_ALWAYS_FATAL_IF(!sProtectedContentVulkanInterface.takeOwnership(),
+                            "SkiaVkRenderEngine couldn't take ownership of existing protected "
+                            "VulkanInterface! Only one SkiaVkRenderEngine instance may exist at a "
+                            "time.");
+    }
 
     SkiaRenderEngine::Contexts contexts;
-    contexts.first = GrDirectContexts::MakeVulkan(sVulkanInterface.getBackendContext(), options);
+    contexts.first = createContext(sVulkanInterface);
     if (supportsProtectedContentImpl()) {
-        contexts.second =
-                GrDirectContexts::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
-                                             options);
+        contexts.second = createContext(sProtectedContentVulkanInterface);
     }
 
     return contexts;
 }
 
 bool SkiaVkRenderEngine::supportsProtectedContentImpl() const {
-    return sProtectedContentVulkanInterface.initialized;
+    return sProtectedContentVulkanInterface.isInitialized();
 }
 
 bool SkiaVkRenderEngine::useProtectedContextImpl(GrProtected) {
     return true;
 }
 
-static void delete_semaphore(void* semaphore) {
-    DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(semaphore);
-    --info->mRefs;
-    if (!info->mRefs) {
-        sVulkanInterface.destroySemaphore(info->mSemaphore);
-        delete info;
-    }
-}
-
-static void delete_semaphore_protected(void* semaphore) {
-    DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(semaphore);
-    --info->mRefs;
-    if (!info->mRefs) {
-        sProtectedContentVulkanInterface.destroySemaphore(info->mSemaphore);
-        delete info;
-    }
-}
-
-static VulkanInterface& getVulkanInterface(bool protectedContext) {
+VulkanInterface& SkiaVkRenderEngine::getVulkanInterface(bool protectedContext) {
     if (protectedContext) {
         return sProtectedContentVulkanInterface;
     }
     return sVulkanInterface;
 }
 
-void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) {
-    if (fenceFd.get() < 0) return;
-
-    int dupedFd = dup(fenceFd.get());
-    if (dupedFd < 0) {
-        ALOGE("failed to create duplicate fence fd: %d", dupedFd);
-        sync_wait(fenceFd.get(), -1);
-        return;
-    }
-
-    base::unique_fd fenceDup(dupedFd);
-    VkSemaphore waitSemaphore =
-            getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
-    GrBackendSemaphore beSemaphore;
-    beSemaphore.initVulkan(waitSemaphore);
-    grContext->wait(1, &beSemaphore, true /* delete after wait */);
-}
-
-base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
-    VulkanInterface& vi = getVulkanInterface(isProtected());
-    VkSemaphore semaphore = vi.createExportableSemaphore();
-
-    GrBackendSemaphore backendSemaphore;
-    backendSemaphore.initVulkan(semaphore);
-
-    GrFlushInfo flushInfo;
-    DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
-    if (semaphore != VK_NULL_HANDLE) {
-        destroySemaphoreInfo = new DestroySemaphoreInfo(semaphore);
-        flushInfo.fNumSemaphores = 1;
-        flushInfo.fSignalSemaphores = &backendSemaphore;
-        flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore;
-        flushInfo.fFinishedContext = destroySemaphoreInfo;
-    }
-    GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
-    grContext->submit(GrSyncCpu::kNo);
-    int drawFenceFd = -1;
-    if (semaphore != VK_NULL_HANDLE) {
-        if (GrSemaphoresSubmitted::kYes == submitted) {
-            drawFenceFd = vi.exportSemaphoreSyncFd(semaphore);
-        }
-        // Now that drawFenceFd has been created, we can delete our reference to this semaphore
-        flushInfo.fFinishedProc(destroySemaphoreInfo);
-    }
-    base::unique_fd res(drawFenceFd);
-    return res;
-}
-
 int SkiaVkRenderEngine::getContextPriority() {
     // EGL_CONTEXT_PRIORITY_REALTIME_NV
     constexpr int kRealtimePriority = 0x3357;
-    if (getVulkanInterface(isProtected()).isRealtimePriority) {
+    if (getVulkanInterface(isProtected()).isRealtimePriority()) {
         return kRealtimePriority;
     } else {
         return 0;
@@ -711,21 +172,21 @@
 
 void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
     StringAppendF(&result, "\n ------------RE Vulkan----------\n");
-    StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.initialized);
+    StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.isInitialized());
     StringAppendF(&result, "\n Vulkan protected device initialized: %d\n",
-                  sProtectedContentVulkanInterface.initialized);
+                  sProtectedContentVulkanInterface.isInitialized());
 
-    if (!sVulkanInterface.initialized) {
+    if (!sVulkanInterface.isInitialized()) {
         return;
     }
 
     StringAppendF(&result, "\n Instance extensions:\n");
-    for (const auto& name : sVulkanInterface.instanceExtensionNames) {
+    for (const auto& name : sVulkanInterface.getInstanceExtensionNames()) {
         StringAppendF(&result, "\n %s\n", name.c_str());
     }
 
     StringAppendF(&result, "\n Device extensions:\n");
-    for (const auto& name : sVulkanInterface.deviceExtensionNames) {
+    for (const auto& name : sVulkanInterface.getDeviceExtensionNames()) {
         StringAppendF(&result, "\n %s\n", name.c_str());
     }
 }
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index 2e0cf45..0a2f9b2 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -20,6 +20,8 @@
 #include <vk/GrVkBackendContext.h>
 
 #include "SkiaRenderEngine.h"
+#include "VulkanInterface.h"
+#include "compat/SkiaGpuContext.h"
 
 namespace android {
 namespace renderengine {
@@ -27,28 +29,64 @@
 
 class SkiaVkRenderEngine : public SkiaRenderEngine {
 public:
-    // Returns false if Vulkan implementation can't support SkiaVkRenderEngine.
-    static bool canSupportSkiaVkRenderEngine();
-    static std::unique_ptr<SkiaVkRenderEngine> create(const RenderEngineCreationArgs& args);
     ~SkiaVkRenderEngine() override;
 
     int getContextPriority() override;
 
+    class DestroySemaphoreInfo {
+    public:
+        DestroySemaphoreInfo() = delete;
+        DestroySemaphoreInfo(const DestroySemaphoreInfo&) = delete;
+        DestroySemaphoreInfo& operator=(const DestroySemaphoreInfo&) = delete;
+        DestroySemaphoreInfo& operator=(DestroySemaphoreInfo&&) = delete;
+
+        DestroySemaphoreInfo(VulkanInterface& vulkanInterface, std::vector<VkSemaphore> semaphores)
+              : mVulkanInterface(vulkanInterface), mSemaphores(std::move(semaphores)) {}
+        DestroySemaphoreInfo(VulkanInterface& vulkanInterface, VkSemaphore semaphore)
+              : DestroySemaphoreInfo(vulkanInterface, std::vector<VkSemaphore>(1, semaphore)) {}
+
+        void unref() {
+            --mRefs;
+            if (!mRefs) {
+                for (VkSemaphore semaphore : mSemaphores) {
+                    mVulkanInterface.destroySemaphore(semaphore);
+                }
+                delete this;
+            }
+        }
+
+    private:
+        ~DestroySemaphoreInfo() = default;
+
+        VulkanInterface& mVulkanInterface;
+        std::vector<VkSemaphore> mSemaphores;
+        // We need to make sure we don't delete the VkSemaphore until it is done being used by both
+        // Skia (including by the GPU) and inside SkiaVkRenderEngine. So we always start with two
+        // refs, one owned by Skia and one owned by the SkiaVkRenderEngine. The refs are decremented
+        // each time unref() is called on this object. Skia will call unref() once it is done with
+        // the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine calls
+        // unref() after sending the semaphore to Skia and exporting it if need be.
+        int mRefs = 2;
+    };
+
 protected:
+    virtual std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) = 0;
+    // Redeclare parent functions that Ganesh vs. Graphite subclasses must implement.
+    virtual void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override = 0;
+    virtual base::unique_fd flushAndSubmit(SkiaGpuContext* context,
+                                           sk_sp<SkSurface> dstSurface) override = 0;
+
+    SkiaVkRenderEngine(const RenderEngineCreationArgs& args);
+
     // Implementations of abstract SkiaRenderEngine functions specific to
-    // rendering backend
-    virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+    // Vulkan, but shareable between Ganesh and Graphite.
+    SkiaRenderEngine::Contexts createContexts() override;
     bool supportsProtectedContentImpl() const override;
     bool useProtectedContextImpl(GrProtected isProtected) override;
-    void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override;
-    base::unique_fd flushAndSubmit(GrDirectContext* context) override;
     void appendBackendSpecificInfoToDump(std::string& result) override;
 
-private:
-    SkiaVkRenderEngine(const RenderEngineCreationArgs& args);
-    base::unique_fd flush();
-
-    GrVkBackendContext mBackendContext;
+    // TODO: b/300533018 - refactor this to be non-static
+    static VulkanInterface& getVulkanInterface(bool protectedContext);
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
new file mode 100644
index 0000000..fc16c56
--- /dev/null
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -0,0 +1,620 @@
+/*
+ * Copyright 2024 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 "RenderEngine"
+
+#include "VulkanInterface.h"
+
+#include <include/gpu/GpuTypes.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
+
+#include <log/log_main.h>
+#include <utils/Timers.h>
+
+#include <cinttypes>
+#include <sstream>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+GrVkBackendContext VulkanInterface::getGaneshBackendContext() {
+    GrVkBackendContext backendContext;
+    backendContext.fInstance = mInstance;
+    backendContext.fPhysicalDevice = mPhysicalDevice;
+    backendContext.fDevice = mDevice;
+    backendContext.fQueue = mQueue;
+    backendContext.fGraphicsQueueIndex = mQueueIndex;
+    backendContext.fMaxAPIVersion = mApiVersion;
+    backendContext.fVkExtensions = &mGrExtensions;
+    backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
+    backendContext.fGetProc = mGrGetProc;
+    backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
+    backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
+    backendContext.fDeviceLostProc = onVkDeviceFault;
+    return backendContext;
+};
+
+VulkanBackendContext VulkanInterface::getGraphiteBackendContext() {
+    VulkanBackendContext backendContext;
+    backendContext.fInstance = mInstance;
+    backendContext.fPhysicalDevice = mPhysicalDevice;
+    backendContext.fDevice = mDevice;
+    backendContext.fQueue = mQueue;
+    backendContext.fGraphicsQueueIndex = mQueueIndex;
+    backendContext.fMaxAPIVersion = mApiVersion;
+    backendContext.fVkExtensions = &mGrExtensions;
+    backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
+    backendContext.fGetProc = mGrGetProc;
+    backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
+    backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
+    backendContext.fDeviceLostProc = onVkDeviceFault;
+    return backendContext;
+};
+
+VkSemaphore VulkanInterface::createExportableSemaphore() {
+    VkExportSemaphoreCreateInfo exportInfo;
+    exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
+    exportInfo.pNext = nullptr;
+    exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+    VkSemaphoreCreateInfo semaphoreInfo;
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    semaphoreInfo.pNext = &exportInfo;
+    semaphoreInfo.flags = 0;
+
+    VkSemaphore semaphore;
+    VkResult err = mFuncs.vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+    if (VK_SUCCESS != err) {
+        ALOGE("%s: failed to create semaphore. err %d\n", __func__, err);
+        return VK_NULL_HANDLE;
+    }
+
+    return semaphore;
+}
+
+// syncFd cannot be <= 0
+VkSemaphore VulkanInterface::importSemaphoreFromSyncFd(int syncFd) {
+    VkSemaphoreCreateInfo semaphoreInfo;
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    semaphoreInfo.pNext = nullptr;
+    semaphoreInfo.flags = 0;
+
+    VkSemaphore semaphore;
+    VkResult err = mFuncs.vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+    if (VK_SUCCESS != err) {
+        ALOGE("%s: failed to create import semaphore", __func__);
+        return VK_NULL_HANDLE;
+    }
+
+    VkImportSemaphoreFdInfoKHR importInfo;
+    importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
+    importInfo.pNext = nullptr;
+    importInfo.semaphore = semaphore;
+    importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;
+    importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+    importInfo.fd = syncFd;
+
+    err = mFuncs.vkImportSemaphoreFdKHR(mDevice, &importInfo);
+    if (VK_SUCCESS != err) {
+        mFuncs.vkDestroySemaphore(mDevice, semaphore, nullptr);
+        ALOGE("%s: failed to import semaphore", __func__);
+        return VK_NULL_HANDLE;
+    }
+
+    return semaphore;
+}
+
+int VulkanInterface::exportSemaphoreSyncFd(VkSemaphore semaphore) {
+    int res;
+
+    VkSemaphoreGetFdInfoKHR getFdInfo;
+    getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+    getFdInfo.pNext = nullptr;
+    getFdInfo.semaphore = semaphore;
+    getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+    VkResult err = mFuncs.vkGetSemaphoreFdKHR(mDevice, &getFdInfo, &res);
+    if (VK_SUCCESS != err) {
+        ALOGE("%s: failed to export semaphore, err: %d", __func__, err);
+        return -1;
+    }
+    return res;
+}
+
+void VulkanInterface::destroySemaphore(VkSemaphore semaphore) {
+    mFuncs.vkDestroySemaphore(mDevice, semaphore, nullptr);
+}
+
+void VulkanInterface::onVkDeviceFault(void* callbackContext, const std::string& description,
+                                      const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                                      const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                                      const std::vector<std::byte>& vendorBinaryData) {
+    VulkanInterface* interface = static_cast<VulkanInterface*>(callbackContext);
+    const std::string protectedStr = interface->mIsProtected ? "protected" : "non-protected";
+    // The final crash string should contain as much differentiating info as possible, up to 1024
+    // bytes. As this final message is constructed, the same information is also dumped to the logs
+    // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+    // statement is always placed first to give context.
+    ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str());
+    std::stringstream crashMsg;
+    crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr;
+
+    if (!addressInfos.empty()) {
+        ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+        crashMsg << ", " << addressInfos.size() << " address info (";
+        for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+            ALOGE(" addressType:       %d", (int)addressInfo.addressType);
+            ALOGE("  reportedAddress:  %" PRIu64, addressInfo.reportedAddress);
+            ALOGE("  addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+            crashMsg << addressInfo.addressType << ":" << addressInfo.reportedAddress << ":"
+                     << addressInfo.addressPrecision << ", ";
+        }
+        crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+        crashMsg << ")";
+    }
+
+    if (!vendorInfos.empty()) {
+        ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+        crashMsg << ", " << vendorInfos.size() << " vendor info (";
+        for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+            ALOGE(" description:      %s", vendorInfo.description);
+            ALOGE("  vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+            ALOGE("  vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+            // Omit descriptions for individual vendor info structs in the crash string, as the
+            // fault code and fault data fields should be enough for clustering, and the verbosity
+            // isn't worth it. Additionally, vendors may just set the general description field of
+            // the overall fault to the description of the first element in this list, and that
+            // overall description will be placed at the end of the crash string.
+            crashMsg << vendorInfo.vendorFaultCode << ":" << vendorInfo.vendorFaultData << ", ";
+        }
+        crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+        crashMsg << ")";
+    }
+
+    if (!vendorBinaryData.empty()) {
+        // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+        ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+              " Stack team if you observe this message).",
+              vendorBinaryData.size());
+        crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+    }
+
+    crashMsg << "): " << description;
+    LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+};
+
+static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
+    if (device != VK_NULL_HANDLE) {
+        return vkGetDeviceProcAddr(device, proc_name);
+    }
+    return vkGetInstanceProcAddr(instance, proc_name);
+};
+
+#define BAIL(fmt, ...)                                          \
+    {                                                           \
+        ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \
+        return;                                                 \
+    }
+
+#define CHECK_NONNULL(expr)       \
+    if ((expr) == nullptr) {      \
+        BAIL("[%s] null", #expr); \
+    }
+
+#define VK_CHECK(expr)                              \
+    if ((expr) != VK_SUCCESS) {                     \
+        BAIL("[%s] failed. err = %d", #expr, expr); \
+        return;                                     \
+    }
+
+#define VK_GET_PROC(F)                                                           \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+#define VK_GET_INST_PROC(instance, F)                                      \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+#define VK_GET_DEV_PROC(device, F)                                     \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+
+void VulkanInterface::init(bool protectedContent) {
+    if (isInitialized()) {
+        ALOGW("Called init on already initialized VulkanInterface");
+        return;
+    }
+
+    const nsecs_t timeBefore = systemTime();
+
+    VK_GET_PROC(EnumerateInstanceVersion);
+    uint32_t instanceVersion;
+    VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion));
+
+    if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
+        BAIL("Vulkan instance API version %" PRIu32 ".%" PRIu32 ".%" PRIu32 " < 1.1.0",
+             VK_VERSION_MAJOR(instanceVersion), VK_VERSION_MINOR(instanceVersion),
+             VK_VERSION_PATCH(instanceVersion));
+    }
+
+    const VkApplicationInfo appInfo = {
+            VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0,
+            VK_MAKE_VERSION(1, 1, 0),
+    };
+
+    VK_GET_PROC(EnumerateInstanceExtensionProperties);
+
+    uint32_t extensionCount = 0;
+    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
+    std::vector<VkExtensionProperties> instanceExtensions(extensionCount);
+    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
+                                                    instanceExtensions.data()));
+    std::vector<const char*> enabledInstanceExtensionNames;
+    enabledInstanceExtensionNames.reserve(instanceExtensions.size());
+    mInstanceExtensionNames.reserve(instanceExtensions.size());
+    for (const auto& instExt : instanceExtensions) {
+        enabledInstanceExtensionNames.push_back(instExt.extensionName);
+        mInstanceExtensionNames.push_back(instExt.extensionName);
+    }
+
+    const VkInstanceCreateInfo instanceCreateInfo = {
+            VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+            nullptr,
+            0,
+            &appInfo,
+            0,
+            nullptr,
+            (uint32_t)enabledInstanceExtensionNames.size(),
+            enabledInstanceExtensionNames.data(),
+    };
+
+    VK_GET_PROC(CreateInstance);
+    VkInstance instance;
+    VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
+
+    VK_GET_INST_PROC(instance, DestroyInstance);
+    mFuncs.vkDestroyInstance = vkDestroyInstance;
+    VK_GET_INST_PROC(instance, EnumeratePhysicalDevices);
+    VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2);
+    VK_GET_INST_PROC(instance, CreateDevice);
+
+    uint32_t physdevCount;
+    VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr));
+    if (physdevCount == 0) {
+        BAIL("Could not find any physical devices");
+    }
+
+    physdevCount = 1;
+    VkPhysicalDevice physicalDevice;
+    VkResult enumeratePhysDevsErr =
+            vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice);
+    if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) {
+        BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d",
+             enumeratePhysDevsErr);
+    }
+
+    VkPhysicalDeviceProperties2 physDevProps = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
+            0,
+            {},
+    };
+    VkPhysicalDeviceProtectedMemoryProperties protMemProps = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
+            0,
+            {},
+    };
+
+    if (protectedContent) {
+        physDevProps.pNext = &protMemProps;
+    }
+
+    vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
+    const uint32_t physicalDeviceApiVersion = physDevProps.properties.apiVersion;
+    if (physicalDeviceApiVersion < VK_MAKE_VERSION(1, 1, 0)) {
+        BAIL("Vulkan physical device API version %" PRIu32 ".%" PRIu32 ".%" PRIu32 " < 1.1.0",
+             VK_VERSION_MAJOR(physicalDeviceApiVersion), VK_VERSION_MINOR(physicalDeviceApiVersion),
+             VK_VERSION_PATCH(physicalDeviceApiVersion));
+    }
+
+    // Check for syncfd support. Bail if we cannot both import and export them.
+    VkPhysicalDeviceExternalSemaphoreInfo semInfo = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+            nullptr,
+            VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+    };
+    VkExternalSemaphoreProperties semProps = {
+            VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0,
+    };
+    vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps);
+
+    bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes &
+                                             VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+            (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) &&
+            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+
+    if (!sufficientSemaphoreSyncFdSupport) {
+        BAIL("Vulkan device does not support sufficient external semaphore sync fd features. "
+             "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+             "compatibleHandleTypes 0x%x (needed 0x%x) "
+             "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+             semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+             semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+             semProps.externalSemaphoreFeatures,
+             VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+                     VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+    } else {
+        ALOGD("Vulkan device supports sufficient external semaphore sync fd features. "
+              "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+              "compatibleHandleTypes 0x%x (needed 0x%x) "
+              "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+              semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+              semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+              semProps.externalSemaphoreFeatures,
+              VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+                      VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+    }
+
+    uint32_t queueCount;
+    vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr);
+    if (queueCount == 0) {
+        BAIL("Could not find queues for physical device");
+    }
+
+    std::vector<VkQueueFamilyProperties2> queueProps(queueCount);
+    std::vector<VkQueueFamilyGlobalPriorityPropertiesEXT> queuePriorityProps(queueCount);
+    VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
+    // Even though we don't yet know if the VK_EXT_global_priority extension is available,
+    // we can safely add the request to the pNext chain, and if the extension is not
+    // available, it will be ignored.
+    for (uint32_t i = 0; i < queueCount; ++i) {
+        queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
+        queuePriorityProps[i].pNext = nullptr;
+        queueProps[i].pNext = &queuePriorityProps[i];
+    }
+    vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data());
+
+    int graphicsQueueIndex = -1;
+    for (uint32_t i = 0; i < queueCount; ++i) {
+        // Look at potential answers to the VK_EXT_global_priority query.  If answers were
+        // provided, we may adjust the queuePriority.
+        if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+            for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) {
+                if (queuePriorityProps[i].priorities[j] > queuePriority) {
+                    queuePriority = queuePriorityProps[i].priorities[j];
+                }
+            }
+            if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) {
+                mIsRealtimePriority = true;
+            }
+            graphicsQueueIndex = i;
+            break;
+        }
+    }
+
+    if (graphicsQueueIndex == -1) {
+        BAIL("Could not find a graphics queue family");
+    }
+
+    uint32_t deviceExtensionCount;
+    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+                                                  nullptr));
+    std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
+    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+                                                  deviceExtensions.data()));
+
+    std::vector<const char*> enabledDeviceExtensionNames;
+    enabledDeviceExtensionNames.reserve(deviceExtensions.size());
+    mDeviceExtensionNames.reserve(deviceExtensions.size());
+    for (const auto& devExt : deviceExtensions) {
+        enabledDeviceExtensionNames.push_back(devExt.extensionName);
+        mDeviceExtensionNames.push_back(devExt.extensionName);
+    }
+
+    mGrExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
+                       enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
+                       enabledDeviceExtensionNames.data());
+
+    if (!mGrExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
+        BAIL("Vulkan driver doesn't support external semaphore fd");
+    }
+
+    mPhysicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2;
+    mPhysicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+    mPhysicalDeviceFeatures2->pNext = nullptr;
+
+    mSamplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures;
+    mSamplerYcbcrConversionFeatures->sType =
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
+    mSamplerYcbcrConversionFeatures->pNext = nullptr;
+
+    mPhysicalDeviceFeatures2->pNext = mSamplerYcbcrConversionFeatures;
+    void** tailPnext = &mSamplerYcbcrConversionFeatures->pNext;
+
+    if (protectedContent) {
+        mProtectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures;
+        mProtectedMemoryFeatures->sType =
+                VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
+        mProtectedMemoryFeatures->pNext = nullptr;
+        *tailPnext = mProtectedMemoryFeatures;
+        tailPnext = &mProtectedMemoryFeatures->pNext;
+    }
+
+    if (mGrExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+        mDeviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
+        mDeviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+        mDeviceFaultFeatures->pNext = nullptr;
+        *tailPnext = mDeviceFaultFeatures;
+        tailPnext = &mDeviceFaultFeatures->pNext;
+    }
+
+    vkGetPhysicalDeviceFeatures2(physicalDevice, mPhysicalDeviceFeatures2);
+    // Looks like this would slow things down and we can't depend on it on all platforms
+    mPhysicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
+
+    if (protectedContent && !mProtectedMemoryFeatures->protectedMemory) {
+        BAIL("Protected memory not supported");
+    }
+
+    float queuePriorities[1] = {0.0f};
+    void* queueNextPtr = nullptr;
+
+    VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT,
+            nullptr,
+            // If queue priority is supported, RE should always have realtime priority.
+            queuePriority,
+    };
+
+    if (mGrExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+        queueNextPtr = &queuePriorityCreateInfo;
+    }
+
+    VkDeviceQueueCreateFlags deviceQueueCreateFlags =
+            (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0);
+
+    const VkDeviceQueueCreateInfo queueInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+            queueNextPtr,
+            deviceQueueCreateFlags,
+            (uint32_t)graphicsQueueIndex,
+            1,
+            queuePriorities,
+    };
+
+    const VkDeviceCreateInfo deviceInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+            mPhysicalDeviceFeatures2,
+            0,
+            1,
+            &queueInfo,
+            0,
+            nullptr,
+            (uint32_t)enabledDeviceExtensionNames.size(),
+            enabledDeviceExtensionNames.data(),
+            nullptr,
+    };
+
+    ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent);
+    VkDevice device;
+    VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device));
+    ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
+
+    VkQueue graphicsQueue;
+    VK_GET_DEV_PROC(device, GetDeviceQueue2);
+    const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr,
+                                                 deviceQueueCreateFlags,
+                                                 (uint32_t)graphicsQueueIndex, 0};
+    vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue);
+
+    VK_GET_DEV_PROC(device, DeviceWaitIdle);
+    VK_GET_DEV_PROC(device, DestroyDevice);
+    mFuncs.vkDeviceWaitIdle = vkDeviceWaitIdle;
+    mFuncs.vkDestroyDevice = vkDestroyDevice;
+
+    VK_GET_DEV_PROC(device, CreateSemaphore);
+    VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR);
+    VK_GET_DEV_PROC(device, GetSemaphoreFdKHR);
+    VK_GET_DEV_PROC(device, DestroySemaphore);
+    mFuncs.vkCreateSemaphore = vkCreateSemaphore;
+    mFuncs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR;
+    mFuncs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR;
+    mFuncs.vkDestroySemaphore = vkDestroySemaphore;
+
+    // At this point, everything's succeeded and we can continue
+    mInitialized = true;
+    mInstance = instance;
+    mPhysicalDevice = physicalDevice;
+    mDevice = device;
+    mQueue = graphicsQueue;
+    mQueueIndex = graphicsQueueIndex;
+    mApiVersion = physicalDeviceApiVersion;
+    // grExtensions already constructed
+    // feature pointers already constructed
+    mGrGetProc = sGetProc;
+    mIsProtected = protectedContent;
+    // mIsRealtimePriority already initialized by constructor
+    // funcs already initialized
+
+    const nsecs_t timeAfter = systemTime();
+    const float initTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
+    ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs);
+}
+
+bool VulkanInterface::takeOwnership() {
+    if (!isInitialized() || mIsOwned) {
+        return false;
+    }
+    mIsOwned = true;
+    return true;
+}
+
+void VulkanInterface::teardown() {
+    // Core resources that must be destroyed using Vulkan functions.
+    if (mDevice != VK_NULL_HANDLE) {
+        mFuncs.vkDeviceWaitIdle(mDevice);
+        mFuncs.vkDestroyDevice(mDevice, nullptr);
+        mDevice = VK_NULL_HANDLE;
+    }
+    if (mInstance != VK_NULL_HANDLE) {
+        mFuncs.vkDestroyInstance(mInstance, nullptr);
+        mInstance = VK_NULL_HANDLE;
+    }
+
+    // Optional features that can be deleted directly.
+    // TODO: b/293371537 - This section should likely be improved to walk the pNext chain of
+    // mPhysicalDeviceFeatures2 and free everything like HWUI's VulkanManager.
+    if (mProtectedMemoryFeatures) {
+        delete mProtectedMemoryFeatures;
+        mProtectedMemoryFeatures = nullptr;
+    }
+    if (mSamplerYcbcrConversionFeatures) {
+        delete mSamplerYcbcrConversionFeatures;
+        mSamplerYcbcrConversionFeatures = nullptr;
+    }
+    if (mPhysicalDeviceFeatures2) {
+        delete mPhysicalDeviceFeatures2;
+        mPhysicalDeviceFeatures2 = nullptr;
+    }
+    if (mDeviceFaultFeatures) {
+        delete mDeviceFaultFeatures;
+        mDeviceFaultFeatures = nullptr;
+    }
+
+    // Misc. fields that can be trivially reset without special deletion:
+    mInitialized = false;
+    mIsOwned = false;
+    mPhysicalDevice = VK_NULL_HANDLE; // Implicitly destroyed by destroying mInstance.
+    mQueue = VK_NULL_HANDLE;          // Implicitly destroyed by destroying mDevice.
+    mQueueIndex = 0;
+    mApiVersion = 0;
+    mGrExtensions = GrVkExtensions();
+    mGrGetProc = nullptr;
+    mIsProtected = false;
+    mIsRealtimePriority = false;
+
+    mFuncs = VulkanFuncs();
+
+    mInstanceExtensionNames.clear();
+    mDeviceExtensionNames.clear();
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
new file mode 100644
index 0000000..af0489a
--- /dev/null
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2024 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 <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/GrVkExtensions.h>
+
+#include <vulkan/vulkan.h>
+
+using namespace skgpu;
+
+namespace skgpu {
+struct VulkanBackendContext;
+} // namespace skgpu
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+class VulkanInterface {
+public:
+    // Create an uninitialized interface. Initialize with `init`.
+    VulkanInterface() = default;
+    ~VulkanInterface() = default;
+    VulkanInterface(const VulkanInterface&) = delete;
+    VulkanInterface& operator=(const VulkanInterface&) = delete;
+    VulkanInterface& operator=(VulkanInterface&&) = delete;
+
+    void init(bool protectedContent = false);
+    // Returns true and marks this VulkanInterface as "owned" if it is initialized but unused by any
+    // RenderEngine instances. Returns false if already owned, indicating that it must not be used
+    // by a new RE instance.
+    bool takeOwnership();
+    void teardown();
+
+    GrVkBackendContext getGaneshBackendContext();
+    VulkanBackendContext getGraphiteBackendContext();
+    VkSemaphore createExportableSemaphore();
+    VkSemaphore importSemaphoreFromSyncFd(int syncFd);
+    int exportSemaphoreSyncFd(VkSemaphore semaphore);
+    void destroySemaphore(VkSemaphore semaphore);
+
+    bool isInitialized() const { return mInitialized; }
+    bool isRealtimePriority() const { return mIsRealtimePriority; }
+    const std::vector<std::string>& getInstanceExtensionNames() { return mInstanceExtensionNames; }
+    const std::vector<std::string>& getDeviceExtensionNames() { return mDeviceExtensionNames; }
+
+private:
+    struct VulkanFuncs {
+        PFN_vkCreateSemaphore vkCreateSemaphore = nullptr;
+        PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr;
+        PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr;
+        PFN_vkDestroySemaphore vkDestroySemaphore = nullptr;
+
+        PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr;
+        PFN_vkDestroyDevice vkDestroyDevice = nullptr;
+        PFN_vkDestroyInstance vkDestroyInstance = nullptr;
+    };
+
+    static void onVkDeviceFault(void* callbackContext, const std::string& description,
+                                const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                                const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                                const std::vector<std::byte>& vendorBinaryData);
+
+    // Note: keep all field defaults in sync with teardown()
+    bool mInitialized = false;
+    bool mIsOwned = false;
+    VkInstance mInstance = VK_NULL_HANDLE;
+    VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
+    VkDevice mDevice = VK_NULL_HANDLE;
+    VkQueue mQueue = VK_NULL_HANDLE;
+    int mQueueIndex = 0;
+    uint32_t mApiVersion = 0;
+    GrVkExtensions mGrExtensions;
+    VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr;
+    VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr;
+    VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr;
+    VkPhysicalDeviceFaultFeaturesEXT* mDeviceFaultFeatures = nullptr;
+    GrVkGetProc mGrGetProc = nullptr;
+    bool mIsProtected = false;
+    bool mIsRealtimePriority = false;
+
+    VulkanFuncs mFuncs;
+
+    std::vector<std::string> mInstanceExtensionNames;
+    std::vector<std::string> mDeviceExtensionNames;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.cpp b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
new file mode 100644
index 0000000..d246466
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2024 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 "GaneshBackendTexture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <include/core/SkImage.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/vk/GrVkTypes.h>
+
+#include "skia/ColorSpaces.h"
+#include "skia/compat/SkiaBackendTexture.h"
+
+#include <android/hardware_buffer.h>
+#include <log/log_main.h>
+#include <utils/Trace.h>
+
+namespace android::renderengine::skia {
+
+GaneshBackendTexture::GaneshBackendTexture(sk_sp<GrDirectContext> grContext,
+                                           AHardwareBuffer* buffer, bool isOutputBuffer)
+      : SkiaBackendTexture(buffer, isOutputBuffer), mGrContext(grContext) {
+    ATRACE_CALL();
+    AHardwareBuffer_Desc desc;
+    AHardwareBuffer_describe(buffer, &desc);
+    const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
+
+    GrBackendFormat backendFormat;
+    const GrBackendApi graphicsApi = grContext->backend();
+    if (graphicsApi == GrBackendApi::kOpenGL) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetGLBackendFormat(grContext.get(), desc.format, false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeGLBackendTexture(grContext.get(), buffer, desc.width,
+                                                             desc.height, &mDeleteProc,
+                                                             &mUpdateProc, &mImageCtx,
+                                                             createProtectedImage, backendFormat,
+                                                             isOutputBuffer);
+    } else if (graphicsApi == GrBackendApi::kVulkan) {
+        backendFormat = GrAHardwareBufferUtils::GetVulkanBackendFormat(grContext.get(), buffer,
+                                                                       desc.format, false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeVulkanBackendTexture(grContext.get(), buffer,
+                                                                 desc.width, desc.height,
+                                                                 &mDeleteProc, &mUpdateProc,
+                                                                 &mImageCtx, createProtectedImage,
+                                                                 backendFormat, isOutputBuffer);
+    } else {
+        LOG_ALWAYS_FATAL("Unexpected graphics API %u", static_cast<unsigned>(graphicsApi));
+    }
+
+    if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
+        LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
+                         "isWriteable:%d format:%d",
+                         this, desc.width, desc.height, createProtectedImage, isOutputBuffer,
+                         desc.format);
+    }
+}
+
+GaneshBackendTexture::~GaneshBackendTexture() {
+    if (mBackendTexture.isValid()) {
+        mDeleteProc(mImageCtx);
+        mBackendTexture = {};
+    }
+}
+
+sk_sp<SkImage> GaneshBackendTexture::makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                                               TextureReleaseProc releaseImageProc,
+                                               ReleaseContext releaseContext) {
+    if (mBackendTexture.isValid()) {
+        mUpdateProc(mImageCtx, mGrContext.get());
+    }
+
+    const SkColorType colorType = colorTypeForImage(alphaType);
+    sk_sp<SkImage> image =
+            SkImages::BorrowTextureFrom(mGrContext.get(), mBackendTexture, kTopLeft_GrSurfaceOrigin,
+                                        colorType, alphaType, toSkColorSpace(dataspace),
+                                        releaseImageProc, releaseContext);
+    if (!image) {
+        logFatalTexture("Unable to generate SkImage.", dataspace, colorType);
+    }
+    return image;
+}
+
+sk_sp<SkSurface> GaneshBackendTexture::makeSurface(ui::Dataspace dataspace,
+                                                   TextureReleaseProc releaseSurfaceProc,
+                                                   ReleaseContext releaseContext) {
+    const SkColorType colorType = internalColorType();
+    sk_sp<SkSurface> surface =
+            SkSurfaces::WrapBackendTexture(mGrContext.get(), mBackendTexture,
+                                           kTopLeft_GrSurfaceOrigin, 0, colorType,
+                                           toSkColorSpace(dataspace), nullptr, releaseSurfaceProc,
+                                           releaseContext);
+    if (!surface) {
+        logFatalTexture("Unable to generate SkSurface.", dataspace, colorType);
+    }
+    return surface;
+}
+
+void GaneshBackendTexture::logFatalTexture(const char* msg, ui::Dataspace dataspace,
+                                           SkColorType colorType) {
+    switch (mBackendTexture.backend()) {
+        case GrBackendApi::kOpenGL: {
+            GrGLTextureInfo textureInfo;
+            bool retrievedTextureInfo =
+                    GrBackendTextures::GetGLTextureInfo(mBackendTexture, &textureInfo);
+            LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
+                             "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
+                             "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u"
+                             " colorType %i",
+                             msg, mBackendTexture.isValid(), static_cast<int32_t>(dataspace),
+                             mBackendTexture.width(), mBackendTexture.height(),
+                             mBackendTexture.hasMipmaps(), mBackendTexture.isProtected(),
+                             static_cast<int>(mBackendTexture.textureType()), retrievedTextureInfo,
+                             textureInfo.fTarget, textureInfo.fFormat, colorType);
+            break;
+        }
+        case GrBackendApi::kVulkan: {
+            GrVkImageInfo imageInfo;
+            bool retrievedImageInfo =
+                    GrBackendTextures::GetVkImageInfo(mBackendTexture, &imageInfo);
+            LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
+                             "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
+                             "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
+                             "fSampleCount: %u fLevelCount: %u colorType %i",
+                             msg, mBackendTexture.isValid(), static_cast<int32_t>(dataspace),
+                             mBackendTexture.width(), mBackendTexture.height(),
+                             mBackendTexture.hasMipmaps(), mBackendTexture.isProtected(),
+                             static_cast<int>(mBackendTexture.textureType()), retrievedImageInfo,
+                             imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount,
+                             colorType);
+            break;
+        }
+        default:
+            LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg,
+                             static_cast<unsigned>(mBackendTexture.backend()));
+            break;
+    }
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.h b/libs/renderengine/skia/compat/GaneshBackendTexture.h
new file mode 100644
index 0000000..5cf8647
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshBackendTexture.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 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 "SkiaBackendTexture.h"
+#include "ui/GraphicTypes.h"
+
+#include <include/android/GrAHardwareBufferUtils.h>
+#include <include/core/SkColorSpace.h>
+#include <include/gpu/GrDirectContext.h>
+
+#include <android-base/macros.h>
+
+namespace android::renderengine::skia {
+
+class GaneshBackendTexture : public SkiaBackendTexture {
+public:
+    // Creates an internal GrBackendTexture whose contents come from the provided buffer.
+    GaneshBackendTexture(sk_sp<GrDirectContext> grContext, AHardwareBuffer* buffer,
+                         bool isOutputBuffer);
+
+    ~GaneshBackendTexture() override;
+
+    sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                             TextureReleaseProc releaseImageProc,
+                             ReleaseContext releaseContext) override;
+
+    sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace, TextureReleaseProc releaseSurfaceProc,
+                                 ReleaseContext releaseContext) override;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GaneshBackendTexture);
+
+    void logFatalTexture(const char* msg, ui::Dataspace dataspace, SkColorType colorType);
+
+    const sk_sp<GrDirectContext> mGrContext;
+    GrBackendTexture mBackendTexture;
+    GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
+    GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
+    GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
new file mode 100644
index 0000000..b2eae00
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2024 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 "GaneshGpuContext.h"
+
+#include <include/core/SkImageInfo.h>
+#include <include/core/SkSurface.h>
+#include <include/core/SkTraceMemoryDump.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/GrTypes.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+#include <include/gpu/gl/GrGLInterface.h>
+#include <include/gpu/vk/GrVkBackendContext.h>
+
+#include "../AutoBackendTexture.h"
+#include "GaneshBackendTexture.h"
+#include "skia/compat/SkiaBackendTexture.h"
+
+#include <android-base/macros.h>
+#include <log/log_main.h>
+#include <memory>
+
+namespace android::renderengine::skia {
+
+namespace {
+static GrContextOptions ganeshOptions(GrContextOptions::PersistentCache& skSLCacheMonitor) {
+    GrContextOptions options;
+    options.fDisableDriverCorrectnessWorkarounds = true;
+    options.fDisableDistanceFieldPaths = true;
+    options.fReducedShaderVariations = true;
+    options.fPersistentCache = &skSLCacheMonitor;
+    return options;
+}
+} // namespace
+
+std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeGL_Ganesh(
+        sk_sp<const GrGLInterface> glInterface,
+        GrContextOptions::PersistentCache& skSLCacheMonitor) {
+    return std::make_unique<GaneshGpuContext>(
+            GrDirectContexts::MakeGL(glInterface, ganeshOptions(skSLCacheMonitor)));
+}
+
+std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Ganesh(
+        const GrVkBackendContext& grVkBackendContext,
+        GrContextOptions::PersistentCache& skSLCacheMonitor) {
+    return std::make_unique<GaneshGpuContext>(
+            GrDirectContexts::MakeVulkan(grVkBackendContext, ganeshOptions(skSLCacheMonitor)));
+}
+
+GaneshGpuContext::GaneshGpuContext(sk_sp<GrDirectContext> grContext) : mGrContext(grContext) {
+    LOG_ALWAYS_FATAL_IF(mGrContext.get() == nullptr, "GrDirectContext creation failed");
+}
+
+GaneshGpuContext::~GaneshGpuContext() {
+    mGrContext->flushAndSubmit(GrSyncCpu::kYes);
+    mGrContext->abandonContext();
+};
+
+sk_sp<GrDirectContext> GaneshGpuContext::grDirectContext() {
+    return mGrContext;
+}
+
+std::unique_ptr<SkiaBackendTexture> GaneshGpuContext::makeBackendTexture(AHardwareBuffer* buffer,
+                                                                         bool isOutputBuffer) {
+    return std::make_unique<GaneshBackendTexture>(mGrContext, buffer, isOutputBuffer);
+}
+
+sk_sp<SkSurface> GaneshGpuContext::createRenderTarget(SkImageInfo imageInfo) {
+    constexpr int kSampleCount = 1; // enable AA
+    constexpr SkSurfaceProps* kProps = nullptr;
+    constexpr bool kMipmapped = false;
+    return SkSurfaces::RenderTarget(mGrContext.get(), skgpu::Budgeted::kNo, imageInfo, kSampleCount,
+                                    kTopLeft_GrSurfaceOrigin, kProps, kMipmapped,
+                                    mGrContext->supportsProtectedContent());
+}
+
+size_t GaneshGpuContext::getMaxRenderTargetSize() const {
+    return mGrContext->maxRenderTargetSize();
+};
+
+size_t GaneshGpuContext::getMaxTextureSize() const {
+    return mGrContext->maxTextureSize();
+};
+
+bool GaneshGpuContext::isAbandonedOrDeviceLost() {
+    return mGrContext->abandoned();
+}
+
+void GaneshGpuContext::setResourceCacheLimit(size_t maxResourceBytes) {
+    mGrContext->setResourceCacheLimit(maxResourceBytes);
+}
+
+void GaneshGpuContext::purgeUnlockedScratchResources() {
+    mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
+}
+
+void GaneshGpuContext::resetContextIfApplicable() {
+    mGrContext->resetContext(); // Only applicable to GL
+};
+
+void GaneshGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
+    mGrContext->dumpMemoryStatistics(traceMemoryDump);
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.h b/libs/renderengine/skia/compat/GaneshGpuContext.h
new file mode 100644
index 0000000..aeb1a82
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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 "SkiaGpuContext.h"
+
+#include <android-base/macros.h>
+
+namespace android::renderengine::skia {
+
+class GaneshGpuContext : public SkiaGpuContext {
+public:
+    GaneshGpuContext(sk_sp<GrDirectContext> grContext);
+    ~GaneshGpuContext() override;
+
+    sk_sp<GrDirectContext> grDirectContext() override;
+
+    std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer,
+                                                           bool isOutputBuffer) override;
+
+    sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) override;
+
+    size_t getMaxRenderTargetSize() const override;
+    size_t getMaxTextureSize() const override;
+    bool isAbandonedOrDeviceLost() override;
+    void setResourceCacheLimit(size_t maxResourceBytes) override;
+
+    void purgeUnlockedScratchResources() override;
+    void resetContextIfApplicable() override;
+
+    void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GaneshGpuContext);
+
+    const sk_sp<GrDirectContext> mGrContext;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp b/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp
new file mode 100644
index 0000000..3dd9ed2
--- /dev/null
+++ b/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 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 "GraphiteBackendTexture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <include/core/SkSurfaceProps.h>
+#include <include/gpu/graphite/Image.h>
+#include <include/gpu/graphite/Surface.h>
+#include <include/gpu/graphite/TextureInfo.h>
+
+#include "skia/ColorSpaces.h"
+
+#include <android/hardware_buffer.h>
+#include <inttypes.h>
+#include <log/log_main.h>
+#include <utils/Trace.h>
+
+namespace android::renderengine::skia {
+
+GraphiteBackendTexture::GraphiteBackendTexture(std::shared_ptr<skgpu::graphite::Recorder> recorder,
+                                               AHardwareBuffer* buffer, bool isOutputBuffer)
+      : SkiaBackendTexture(buffer, isOutputBuffer), mRecorder(std::move(recorder)) {
+    ATRACE_CALL();
+    AHardwareBuffer_Desc desc;
+    AHardwareBuffer_describe(buffer, &desc);
+    const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
+
+    const SkISize dimensions = {static_cast<int32_t>(desc.width),
+                                static_cast<int32_t>(desc.height)};
+    LOG_ALWAYS_FATAL_IF(static_cast<uint32_t>(dimensions.width()) != desc.width ||
+                                static_cast<uint32_t>(dimensions.height()) != desc.height,
+                        "Failed to create a valid texture, casting unsigned dimensions [%" PRIu32
+                        ",%" PRIu32 "] to signed [%" PRIo32 ",%" PRIo32 "] "
+                        "is invalid",
+                        desc.width, desc.height, dimensions.width(), dimensions.height());
+
+    mBackendTexture = mRecorder->createBackendTexture(buffer, isOutputBuffer, createProtectedImage,
+                                                      dimensions, false);
+    if (!mBackendTexture.isValid() || !dimensions.width() || !dimensions.height()) {
+        LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
+                         "isWriteable:%d format:%d",
+                         this, dimensions.width(), dimensions.height(), createProtectedImage,
+                         isOutputBuffer, desc.format);
+    }
+}
+
+GraphiteBackendTexture::~GraphiteBackendTexture() {
+    if (mBackendTexture.isValid()) {
+        mRecorder->deleteBackendTexture(mBackendTexture);
+        mBackendTexture = {};
+    }
+}
+
+sk_sp<SkImage> GraphiteBackendTexture::makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                                                 TextureReleaseProc releaseImageProc,
+                                                 ReleaseContext releaseContext) {
+    const SkColorType colorType = colorTypeForImage(alphaType);
+    sk_sp<SkImage> image =
+            SkImages::WrapTexture(mRecorder.get(), mBackendTexture, colorType, alphaType,
+                                  toSkColorSpace(dataspace), releaseImageProc, releaseContext);
+    if (!image) {
+        logFatalTexture("Unable to generate SkImage.", dataspace, colorType);
+    }
+    return image;
+}
+
+sk_sp<SkSurface> GraphiteBackendTexture::makeSurface(ui::Dataspace dataspace,
+                                                     TextureReleaseProc releaseSurfaceProc,
+                                                     ReleaseContext releaseContext) {
+    const SkColorType colorType = internalColorType();
+    SkSurfaceProps props;
+    sk_sp<SkSurface> surface =
+            SkSurfaces::WrapBackendTexture(mRecorder.get(), mBackendTexture, colorType,
+                                           toSkColorSpace(dataspace), &props, releaseSurfaceProc,
+                                           releaseContext);
+    if (!surface) {
+        logFatalTexture("Unable to generate SkSurface.", dataspace, colorType);
+    }
+    return surface;
+}
+
+void GraphiteBackendTexture::logFatalTexture(const char* msg, ui::Dataspace dataspace,
+                                             SkColorType colorType) {
+    // TODO: b/293371537 - Iterate on this logging (validate failure cases, possibly check
+    // VulkanTextureInfo, etc.)
+    const skgpu::graphite::TextureInfo& textureInfo = mBackendTexture.info();
+    LOG_ALWAYS_FATAL("%s isOutputBuffer:%d, dataspace:%d, colorType:%d"
+                     "\n\tBackendTexture: isValid:%d, dimensions:%dx%d"
+                     "\n\t\tTextureInfo: isValid:%d, numSamples:%d, mipmapped:%d, isProtected: %d",
+                     msg, isOutputBuffer(), static_cast<int32_t>(dataspace), colorType,
+                     mBackendTexture.isValid(), mBackendTexture.dimensions().width(),
+                     mBackendTexture.dimensions().height(), textureInfo.isValid(),
+                     textureInfo.numSamples(), static_cast<int32_t>(textureInfo.mipmapped()),
+                     static_cast<int32_t>(textureInfo.isProtected()));
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteBackendTexture.h b/libs/renderengine/skia/compat/GraphiteBackendTexture.h
new file mode 100644
index 0000000..3bec3f7
--- /dev/null
+++ b/libs/renderengine/skia/compat/GraphiteBackendTexture.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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 "SkiaBackendTexture.h"
+
+#include <include/core/SkColorSpace.h>
+#include <include/core/SkImage.h>
+#include <include/core/SkSurface.h>
+#include <include/gpu/graphite/BackendTexture.h>
+#include <include/gpu/graphite/Recorder.h>
+
+#include <android-base/macros.h>
+#include <ui/GraphicTypes.h>
+
+#include <memory>
+
+namespace android::renderengine::skia {
+
+class GraphiteBackendTexture : public SkiaBackendTexture {
+public:
+    // Creates an internal skgpu::graphite::BackendTexture whose contents come from the provided
+    // buffer.
+    GraphiteBackendTexture(std::shared_ptr<skgpu::graphite::Recorder> recorder,
+                           AHardwareBuffer* buffer, bool isOutputBuffer);
+
+    ~GraphiteBackendTexture() override;
+
+    sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                             TextureReleaseProc releaseImageProc,
+                             ReleaseContext releaseContext) override;
+
+    sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace, TextureReleaseProc releaseSurfaceProc,
+                                 ReleaseContext releaseContext) override;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GraphiteBackendTexture);
+
+    void logFatalTexture(const char* msg, ui::Dataspace dataspace, SkColorType colorType);
+
+    const std::shared_ptr<skgpu::graphite::Recorder> mRecorder;
+    skgpu::graphite::BackendTexture mBackendTexture;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
new file mode 100644
index 0000000..69f5832
--- /dev/null
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2024 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 "GraphiteGpuContext.h"
+
+#include <include/core/SkImageInfo.h>
+#include <include/core/SkSurface.h>
+#include <include/core/SkTraceMemoryDump.h>
+#include <include/gpu/graphite/GraphiteTypes.h>
+#include <include/gpu/graphite/Surface.h>
+#include <include/gpu/graphite/vk/VulkanGraphiteUtils.h>
+
+#include "GpuTypes.h"
+#include "skia/compat/GraphiteBackendTexture.h"
+
+#include <android-base/macros.h>
+#include <log/log_main.h>
+#include <memory>
+
+namespace android::renderengine::skia {
+
+namespace {
+static skgpu::graphite::ContextOptions graphiteOptions() {
+    skgpu::graphite::ContextOptions options;
+    options.fDisableDriverCorrectnessWorkarounds = true;
+    return options;
+}
+} // namespace
+
+std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Graphite(
+        const skgpu::VulkanBackendContext& vulkanBackendContext) {
+    return std::make_unique<GraphiteGpuContext>(
+            skgpu::graphite::ContextFactory::MakeVulkan(vulkanBackendContext, graphiteOptions()));
+}
+
+GraphiteGpuContext::GraphiteGpuContext(std::unique_ptr<skgpu::graphite::Context> context)
+      : mContext(std::move(context)) {
+    LOG_ALWAYS_FATAL_IF(mContext.get() == nullptr, "graphite::Context creation failed");
+    LOG_ALWAYS_FATAL_IF(mContext->backend() != skgpu::BackendApi::kVulkan,
+                        "graphite::Context::backend() == %d, but GraphiteBackendContext makes "
+                        "assumptions that are only valid for Vulkan (%d)",
+                        static_cast<int>(mContext->backend()),
+                        static_cast<int>(skgpu::BackendApi::kVulkan));
+
+    // TODO: b/293371537 - Iterate on default cache limits (the Recorder should have the majority of
+    // the budget, and the Context should be given a smaller fraction.)
+    skgpu::graphite::RecorderOptions recorderOptions = skgpu::graphite::RecorderOptions();
+    this->mRecorder = mContext->makeRecorder(recorderOptions);
+    LOG_ALWAYS_FATAL_IF(mRecorder.get() == nullptr, "graphite::Recorder creation failed");
+}
+
+GraphiteGpuContext::~GraphiteGpuContext() {
+    // The equivalent operation would occur when destroying the graphite::Context, but calling this
+    // explicitly allows any outstanding GraphiteBackendTextures to be released, thus allowing us to
+    // assert that this GraphiteGpuContext holds the last ref to the underlying graphite::Recorder.
+    mContext->submit(skgpu::graphite::SyncToCpu::kYes);
+    // We must call the Context's and Recorder's dtors before exiting this function, so all other
+    // refs must be released by now. Note: these assertions may be unreliable in a hypothetical
+    // future world where we take advantage of Graphite's multi-threading capabilities!
+    LOG_ALWAYS_FATAL_IF(mRecorder.use_count() > 1,
+                        "Something other than GraphiteGpuContext holds a ref to the underlying "
+                        "graphite::Recorder");
+    LOG_ALWAYS_FATAL_IF(mContext.use_count() > 1,
+                        "Something other than GraphiteGpuContext holds a ref to the underlying "
+                        "graphite::Context");
+};
+
+std::shared_ptr<skgpu::graphite::Context> GraphiteGpuContext::graphiteContext() {
+    return mContext;
+}
+
+std::shared_ptr<skgpu::graphite::Recorder> GraphiteGpuContext::graphiteRecorder() {
+    return mRecorder;
+}
+
+std::unique_ptr<SkiaBackendTexture> GraphiteGpuContext::makeBackendTexture(AHardwareBuffer* buffer,
+                                                                           bool isOutputBuffer) {
+    return std::make_unique<GraphiteBackendTexture>(graphiteRecorder(), buffer, isOutputBuffer);
+}
+
+sk_sp<SkSurface> GraphiteGpuContext::createRenderTarget(SkImageInfo imageInfo) {
+    constexpr SkSurfaceProps* kProps = nullptr;
+    return SkSurfaces::RenderTarget(mRecorder.get(), imageInfo, skgpu::Mipmapped::kNo, kProps);
+}
+
+size_t GraphiteGpuContext::getMaxRenderTargetSize() const {
+    // maxRenderTargetSize only differs from maxTextureSize on GL, so as long as Graphite implies
+    // Vk, then the distinction is irrelevant.
+    return getMaxTextureSize();
+};
+
+size_t GraphiteGpuContext::getMaxTextureSize() const {
+    return mContext->maxTextureSize();
+};
+
+bool GraphiteGpuContext::isAbandonedOrDeviceLost() {
+    return mContext->isDeviceLost();
+}
+
+void GraphiteGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
+    mContext->dumpMemoryStatistics(traceMemoryDump);
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.h b/libs/renderengine/skia/compat/GraphiteGpuContext.h
new file mode 100644
index 0000000..413817f
--- /dev/null
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 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 "SkiaGpuContext.h"
+#include "graphite/Recorder.h"
+
+#include <android-base/macros.h>
+
+namespace android::renderengine::skia {
+
+class GraphiteGpuContext : public SkiaGpuContext {
+public:
+    GraphiteGpuContext(std::unique_ptr<skgpu::graphite::Context> context);
+    ~GraphiteGpuContext() override;
+
+    std::shared_ptr<skgpu::graphite::Context> graphiteContext() override;
+    std::shared_ptr<skgpu::graphite::Recorder> graphiteRecorder() override;
+
+    std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer,
+                                                           bool isOutputBuffer) override;
+
+    sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) override;
+
+    size_t getMaxRenderTargetSize() const override;
+    size_t getMaxTextureSize() const override;
+    bool isAbandonedOrDeviceLost() override;
+    // No-op (large resources like textures, surfaces, images, etc. created by clients don't count
+    // towards Graphite's internal caching budgets, so adjusting its limits based on display change
+    // events should be unnecessary. Additionally, Graphite doesn't expose many cache tweaking
+    // functions yet, as its design may evolve.)
+    void setResourceCacheLimit(size_t maxResourceBytes) override{};
+
+    // TODO: b/293371537 - Triple-check and validate that no cleanup is necessary when switching
+    // contexts.
+    // No-op (unnecessary during context switch for Graphite's client-budgeted memory model).
+    void purgeUnlockedScratchResources() override{};
+    // No-op (only applicable to GL).
+    void resetContextIfApplicable() override{};
+
+    void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GraphiteGpuContext);
+
+    std::shared_ptr<skgpu::graphite::Context> mContext;
+    std::shared_ptr<skgpu::graphite::Recorder> mRecorder;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/SkiaBackendTexture.h b/libs/renderengine/skia/compat/SkiaBackendTexture.h
new file mode 100644
index 0000000..09877a5
--- /dev/null
+++ b/libs/renderengine/skia/compat/SkiaBackendTexture.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 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 <include/android/GrAHardwareBufferUtils.h>
+#include <include/core/SkColorSpace.h>
+#include <include/gpu/GrDirectContext.h>
+
+#include <android/hardware_buffer.h>
+#include <ui/GraphicTypes.h>
+
+namespace android::renderengine::skia {
+
+/**
+ * Abstraction over a Skia backend-specific texture type.
+ *
+ * This class does not do any lifecycle management, and should typically be wrapped in an
+ * AutoBackendTexture::LocalRef. Typically created via SkiaGpuContext::makeBackendTexture(...).
+ */
+class SkiaBackendTexture {
+public:
+    SkiaBackendTexture(AHardwareBuffer* buffer, bool isOutputBuffer)
+          : mIsOutputBuffer(isOutputBuffer) {
+        AHardwareBuffer_Desc desc;
+        AHardwareBuffer_describe(buffer, &desc);
+
+        mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+    }
+    virtual ~SkiaBackendTexture() = default;
+
+    // These two definitions mirror Skia's own types used for texture release callbacks, which are
+    // re-declared multiple times between context-specific implementation headers for Ganesh vs.
+    // Graphite, and within the context of SkImages vs. SkSurfaces. Our own re-declaration allows us
+    // to not pull in any implementation-specific headers here.
+    using ReleaseContext = void*;
+    using TextureReleaseProc = void (*)(ReleaseContext);
+
+    // Guaranteed to be non-null (crashes otherwise). An opaque alphaType may coerce the internal
+    // color type to RBGX.
+    virtual sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                                     TextureReleaseProc releaseImageProc,
+                                     ReleaseContext releaseContext) = 0;
+
+    // Guaranteed to be non-null (crashes otherwise).
+    virtual sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace,
+                                         TextureReleaseProc releaseSurfaceProc,
+                                         ReleaseContext releaseContext) = 0;
+
+    bool isOutputBuffer() const { return mIsOutputBuffer; }
+
+    SkColorType internalColorType() const { return mColorType; }
+
+protected:
+    // Strip alpha channel from rawColorType if alphaType is opaque (note: only works for RGBA_8888)
+    SkColorType colorTypeForImage(SkAlphaType alphaType) const {
+        if (alphaType == kOpaque_SkAlphaType) {
+            // TODO: b/40043126 - Support RGBX SkColorType for F16 and support it and 101010x as a
+            // source
+            if (internalColorType() == kRGBA_8888_SkColorType) {
+                return kRGB_888x_SkColorType;
+            }
+        }
+        return internalColorType();
+    }
+
+private:
+    const bool mIsOutputBuffer;
+    SkColorType mColorType = kUnknown_SkColorType;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
new file mode 100644
index 0000000..282dfe7
--- /dev/null
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 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
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include <include/core/SkSurface.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/gl/GrGLInterface.h>
+#include <include/gpu/graphite/Context.h>
+#include <include/gpu/vk/GrVkBackendContext.h>
+#include "include/gpu/vk/VulkanBackendContext.h"
+
+#include "SkiaBackendTexture.h"
+
+#include <log/log.h>
+
+#include <memory>
+
+namespace android::renderengine::skia {
+
+/**
+ * Abstraction over Ganesh and Graphite's underlying context-like objects.
+ *
+ * On destruction, subclasses will submit any pending work before destroying their internal Skia
+ * context(s). Any unused cached SkiaBackendTextures created from a SkiaGpuContext that are awaiting
+ * cleanup must be deleted before destroying that SkiaGpuContext, and any textures that are released
+ * during ~SkiaGpuContext must be configured to be deleted immediately.
+ */
+class SkiaGpuContext {
+public:
+    /**
+     * glInterface must remain valid until after SkiaGpuContext is destroyed.
+     */
+    static std::unique_ptr<SkiaGpuContext> MakeGL_Ganesh(
+            sk_sp<const GrGLInterface> glInterface,
+            GrContextOptions::PersistentCache& skSLCacheMonitor);
+
+    /**
+     * grVkBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     */
+    static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
+            const GrVkBackendContext& grVkBackendContext,
+            GrContextOptions::PersistentCache& skSLCacheMonitor);
+
+    // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite.
+    /**
+     * vulkanBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     */
+    static std::unique_ptr<SkiaGpuContext> MakeVulkan_Graphite(
+            const skgpu::VulkanBackendContext& vulkanBackendContext);
+
+    virtual ~SkiaGpuContext() = default;
+
+    /**
+     * Only callable on Ganesh-backed instances of SkiaGpuContext, otherwise fatal.
+     */
+    virtual sk_sp<GrDirectContext> grDirectContext() {
+        LOG_ALWAYS_FATAL("grDirectContext() called on a non-Ganesh instance of SkiaGpuContext!");
+    }
+
+    /**
+     * Only callable on Graphite-backed instances of SkiaGpuContext, otherwise fatal.
+     */
+    virtual std::shared_ptr<skgpu::graphite::Context> graphiteContext() {
+        LOG_ALWAYS_FATAL("graphiteContext() called on a non-Graphite instance of SkiaGpuContext!");
+    }
+
+    /**
+     * Only callable on Graphite-backed instances of SkiaGpuContext, otherwise fatal.
+     */
+    virtual std::shared_ptr<skgpu::graphite::Recorder> graphiteRecorder() {
+        LOG_ALWAYS_FATAL("graphiteRecorder() called on a non-Graphite instance of SkiaGpuContext!");
+    }
+
+    virtual std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer,
+                                                                   bool isOutputBuffer) = 0;
+
+    /**
+     * Notes:
+     * - The surface doesn't count against Skia's caching budgets.
+     * - Protected status is set to match the implementation's underlying context.
+     * - The origin of the surface in texture space corresponds to the top-left content pixel.
+     * - AA is always enabled.
+     */
+    virtual sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) = 0;
+
+    virtual bool isAbandonedOrDeviceLost() = 0;
+    virtual size_t getMaxRenderTargetSize() const = 0;
+    virtual size_t getMaxTextureSize() const = 0;
+    virtual void setResourceCacheLimit(size_t maxResourceBytes) = 0;
+
+    virtual void purgeUnlockedScratchResources() = 0;
+    virtual void resetContextIfApplicable() = 0; // No-op outside of GL (&& Ganesh at this point.)
+
+    virtual void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const = 0;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index 48dc77e..e778884 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -30,7 +30,7 @@
 #include "SkCanvas.h"
 #include "SkRect.h"
 #include "SkTypeface.h"
-#include "src/utils/SkMultiPictureDocument.h"
+#include "include/docs/SkMultiPictureDocument.h"
 #include <sys/stat.h>
 
 namespace android {
@@ -196,7 +196,7 @@
         // procs doesn't need to outlive this Make call
         // The last argument is a callback for the endPage behavior.
         // See SkSharingProc.h for more explanation of this callback.
-        mMultiPic = SkMakeMultiPictureDocument(
+        mMultiPic = SkMultiPictureDocument::Make(
                 mOpenMultiPicStream.get(), &procs,
                 [sharingCtx = mSerialContext.get()](const SkPicture* pic) {
                     SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
diff --git a/libs/renderengine/skia/filters/BlurFilter.h b/libs/renderengine/skia/filters/BlurFilter.h
index 9cddc75..180c922 100644
--- a/libs/renderengine/skia/filters/BlurFilter.h
+++ b/libs/renderengine/skia/filters/BlurFilter.h
@@ -21,6 +21,8 @@
 #include <SkRuntimeEffect.h>
 #include <SkSurface.h>
 
+#include "../compat/SkiaGpuContext.h"
+
 using namespace std;
 
 namespace android {
@@ -38,8 +40,9 @@
     virtual ~BlurFilter(){}
 
     // Execute blur, saving it to a texture
-    virtual sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
-                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const = 0;
+    virtual sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
+                                    const sk_sp<SkImage> blurInput,
+                                    const SkRect& blurRect) const = 0;
 
     /**
      * Draw the blurred content (from the generate method) into the canvas.
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index e72c501..c9499cb 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -42,14 +42,13 @@
 
 GaussianBlurFilter::GaussianBlurFilter(): BlurFilter(/* maxCrossFadeRadius= */ 0.0f) {}
 
-sk_sp<SkImage> GaussianBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
-                                            const sk_sp<SkImage> input, const SkRect& blurRect)
-    const {
+sk_sp<SkImage> GaussianBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
+                                            const sk_sp<SkImage> input,
+                                            const SkRect& blurRect) const {
     // Create blur surface with the bit depth and colorspace of the original surface
     SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
                                                        std::ceil(blurRect.height() * kInputScale));
-    sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context,
-                                                        skgpu::Budgeted::kNo, scaledInfo);
+    sk_sp<SkSurface> surface = context->createRenderTarget(scaledInfo);
 
     SkPaint paint;
     paint.setBlendMode(SkBlendMode::kSrc);
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.h b/libs/renderengine/skia/filters/GaussianBlurFilter.h
index a4febd2..878ab21 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.h
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.h
@@ -37,9 +37,8 @@
     virtual ~GaussianBlurFilter(){}
 
     // Execute blur, saving it to a texture
-    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+    sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
                             const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
-
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index 5c9820c..7a070d7 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -73,8 +73,7 @@
     return surface->makeImageSnapshot();
 }
 
-sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context,
-                                          const uint32_t blurRadius,
+sk_sp<SkImage> KawaseBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
                                           const sk_sp<SkImage> input,
                                           const SkRect& blurRect) const {
     LOG_ALWAYS_FATAL_IF(context == nullptr, "%s: Needs GPU context", __func__);
@@ -108,12 +107,7 @@
             input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
     blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale;
 
-    constexpr int kSampleCount = 1;
-    constexpr bool kMipmapped = false;
-    constexpr SkSurfaceProps* kProps = nullptr;
-    sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kYes, scaledInfo,
-                                                        kSampleCount, kTopLeft_GrSurfaceOrigin,
-                                                        kProps, kMipmapped, input->isProtected());
+    sk_sp<SkSurface> surface = context->createRenderTarget(scaledInfo);
     LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
     sk_sp<SkImage> tmpBlur = makeImage(surface.get(), &blurBuilder);
 
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.h b/libs/renderengine/skia/filters/KawaseBlurFilter.h
index 0ac5ac8..429a537 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.h
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.h
@@ -42,7 +42,7 @@
     virtual ~KawaseBlurFilter(){}
 
     // Execute blur, saving it to a texture
-    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+    sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
                             const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
 
 private:
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index 50e166d..0eea187 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
@@ -44,6 +45,7 @@
         "librenderengine_mocks",
         "libshaders",
         "libtonemap",
+        "libsurfaceflinger_common",
     ],
     header_libs: [
         "libtonemap_headers",
@@ -61,5 +63,6 @@
         "libsync",
         "libui",
         "libutils",
+        "server_configurable_flags",
     ],
 }
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 11d4fde..4dcaff9 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -106,92 +106,63 @@
     virtual ~RenderEngineFactory() = default;
 
     virtual std::string name() = 0;
-    virtual renderengine::RenderEngine::RenderEngineType type() = 0;
-    virtual std::unique_ptr<renderengine::RenderEngine> createRenderEngine() = 0;
-    virtual bool typeSupported() = 0;
-};
-
-class SkiaVkRenderEngineFactory : public RenderEngineFactory {
-public:
-    std::string name() override { return "SkiaVkRenderEngineFactory"; }
-
-    renderengine::RenderEngine::RenderEngineType type() {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_VK;
-    }
-
-    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override {
-        std::unique_ptr<renderengine::RenderEngine> re = createSkiaVkRenderEngine();
-        return re;
-    }
-
-    std::unique_ptr<renderengine::skia::SkiaVkRenderEngine> createSkiaVkRenderEngine() {
+    virtual renderengine::RenderEngine::GraphicsApi graphicsApi() = 0;
+    virtual renderengine::RenderEngine::SkiaBackend skiaBackend() = 0;
+    bool apiSupported() { return renderengine::RenderEngine::canSupport(graphicsApi()); }
+    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() {
         renderengine::RenderEngineCreationArgs reCreationArgs =
                 renderengine::RenderEngineCreationArgs::Builder()
                         .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
                         .setImageCacheSize(1)
                         .setEnableProtectedContext(false)
                         .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
+                        .setBlurAlgorithm(renderengine::RenderEngine::BlurAlgorithm::KAWASE)
                         .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
-                        .setRenderEngineType(type())
+                        .setThreaded(renderengine::RenderEngine::Threaded::NO)
+                        .setGraphicsApi(graphicsApi())
+                        .setSkiaBackend(skiaBackend())
                         .build();
-        return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs);
+        return renderengine::RenderEngine::create(reCreationArgs);
     }
-
-    bool typeSupported() override {
-        return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine();
-    }
-    void skip() { GTEST_SKIP(); }
 };
 
 class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
 public:
     std::string name() override { return "SkiaGLRenderEngineFactory"; }
 
-    renderengine::RenderEngine::RenderEngineType type() {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
+    renderengine::RenderEngine::GraphicsApi graphicsApi() {
+        return renderengine::RenderEngine::GraphicsApi::GL;
     }
 
-    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override {
-        renderengine::RenderEngineCreationArgs reCreationArgs =
-                renderengine::RenderEngineCreationArgs::Builder()
-                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
-                        .setImageCacheSize(1)
-                        .setEnableProtectedContext(false)
-                        .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
-                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
-                        .setRenderEngineType(type())
-                        .build();
-        return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
+    renderengine::RenderEngine::SkiaBackend skiaBackend() override {
+        return renderengine::RenderEngine::SkiaBackend::GANESH;
     }
-
-    bool typeSupported() override { return true; }
 };
 
-class SkiaGLESCMRenderEngineFactory : public RenderEngineFactory {
+class GaneshVkRenderEngineFactory : public RenderEngineFactory {
 public:
-    std::string name() override { return "SkiaGLCMRenderEngineFactory"; }
+    std::string name() override { return "GaneshVkRenderEngineFactory"; }
 
-    renderengine::RenderEngine::RenderEngineType type() {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
+    renderengine::RenderEngine::GraphicsApi graphicsApi() override {
+        return renderengine::RenderEngine::GraphicsApi::VK;
     }
 
-    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override {
-        renderengine::RenderEngineCreationArgs reCreationArgs =
-                renderengine::RenderEngineCreationArgs::Builder()
-                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
-                        .setImageCacheSize(1)
-                        .setEnableProtectedContext(false)
-                        .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
-                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
-                        .setRenderEngineType(type())
-                        .build();
-        return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
+    renderengine::RenderEngine::SkiaBackend skiaBackend() override {
+        return renderengine::RenderEngine::SkiaBackend::GANESH;
+    }
+};
+
+class GraphiteVkRenderEngineFactory : public RenderEngineFactory {
+public:
+    std::string name() override { return "GraphiteVkRenderEngineFactory"; }
+
+    renderengine::RenderEngine::GraphicsApi graphicsApi() override {
+        return renderengine::RenderEngine::GraphicsApi::VK;
     }
 
-    bool typeSupported() override { return true; }
+    renderengine::RenderEngine::SkiaBackend skiaBackend() override {
+        return renderengine::RenderEngine::SkiaBackend::GRAPHITE;
+    }
 };
 
 class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
@@ -271,7 +242,7 @@
     RenderEngineTest() {
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+        ALOGI("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
     }
 
     ~RenderEngineTest() {
@@ -280,7 +251,7 @@
         }
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+        ALOGI("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
     }
 
     void writeBufferToFile(const char* basename) {
@@ -1294,7 +1265,12 @@
 
 void RenderEngineTest::fillBufferWithPremultiplyAlpha() {
     fillRedBufferWithPremultiplyAlpha();
-    expectBufferColor(fullscreenRect(), 128, 0, 0, 128);
+    // Different backends and GPUs may round 255 * 0.5 = 127.5 differently, but
+    // either 127 or 128 are acceptable. Checking both 127 and 128 with a
+    // tolerance of 1 allows either 127 or 128 to pass, while preventing 126 or
+    // 129 from erroneously passing.
+    expectBufferColor(fullscreenRect(), 127, 0, 0, 127, 1);
+    expectBufferColor(fullscreenRect(), 128, 0, 0, 128, 1);
 }
 
 void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() {
@@ -1523,10 +1499,11 @@
 
 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
                          testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
-                                         std::make_shared<SkiaVkRenderEngineFactory>()));
+                                         std::make_shared<GaneshVkRenderEngineFactory>(),
+                                         std::make_shared<GraphiteVkRenderEngineFactory>()));
 
 TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1534,7 +1511,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1561,7 +1538,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1595,7 +1572,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1616,7 +1593,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1624,7 +1601,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1632,7 +1609,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1640,7 +1617,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1648,7 +1625,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1656,7 +1633,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1664,7 +1641,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1672,7 +1649,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1680,7 +1657,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1688,7 +1665,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1696,7 +1673,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1706,7 +1683,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -1717,7 +1694,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -1726,7 +1703,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1734,7 +1711,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1742,7 +1719,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1750,7 +1727,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1758,7 +1735,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1766,7 +1743,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1774,7 +1751,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1782,7 +1759,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1790,7 +1767,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1798,7 +1775,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1806,7 +1783,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1814,7 +1791,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1822,7 +1799,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1830,7 +1807,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1838,7 +1815,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1846,7 +1823,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1856,7 +1833,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -1867,7 +1844,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -1876,7 +1853,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1884,7 +1861,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1892,7 +1869,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1900,7 +1877,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1908,7 +1885,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1916,7 +1893,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1924,7 +1901,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1932,7 +1909,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1940,7 +1917,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1948,7 +1925,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1956,7 +1933,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1964,7 +1941,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1972,7 +1949,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1980,7 +1957,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1988,7 +1965,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1996,7 +1973,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2006,7 +1983,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2017,7 +1994,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2026,7 +2003,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2034,7 +2011,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2042,7 +2019,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2050,7 +2027,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2058,7 +2035,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2066,7 +2043,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2074,7 +2051,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2082,7 +2059,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2090,7 +2067,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2108,7 +2085,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2131,7 +2108,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2155,7 +2132,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2180,7 +2157,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2206,7 +2183,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2235,7 +2212,7 @@
 }
 
 TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2271,7 +2248,7 @@
     if (mRE->canSkipPostRenderCleanup()) {
         // Skia's Vk backend may keep the texture alive beyond drawLayersInternal, so
         // it never gets added to the cleanup list. In those cases, we can skip.
-        EXPECT_TRUE(GetParam()->type() == renderengine::RenderEngine::RenderEngineType::SKIA_VK);
+        EXPECT_TRUE(GetParam()->graphicsApi() == renderengine::RenderEngine::GraphicsApi::VK);
     } else {
         mRE->cleanupPostRender();
         EXPECT_TRUE(mRE->canSkipPostRenderCleanup());
@@ -2279,7 +2256,7 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersCrop) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2332,7 +2309,7 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2380,7 +2357,7 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2417,7 +2394,7 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersXY) {
-    if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2460,7 +2437,7 @@
 }
 
 TEST_P(RenderEngineTest, testClear) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2492,7 +2469,7 @@
 }
 
 TEST_P(RenderEngineTest, testDisableBlendingBuffer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2542,53 +2519,8 @@
     expectBufferColor(rect, 0, 128, 0, 128);
 }
 
-TEST_P(RenderEngineTest, testBorder) {
-    if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) {
-        GTEST_SKIP();
-    }
-
-    initializeRenderEngine();
-
-    const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB;
-
-    const auto displayRect = Rect(1080, 2280);
-    renderengine::DisplaySettings display{
-            .physicalDisplay = displayRect,
-            .clip = displayRect,
-            .outputDataspace = dataspace,
-    };
-    display.borderInfoList.clear();
-    renderengine::BorderRenderInfo info;
-    info.combinedRegion = Region(Rect(99, 99, 199, 199));
-    info.width = 20.0f;
-    info.color = half4{1.0f, 128.0f / 255.0f, 0.0f, 1.0f};
-    display.borderInfoList.emplace_back(info);
-
-    const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255));
-    const renderengine::LayerSettings greenLayer{
-            .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f),
-            .source =
-                    renderengine::PixelSource{
-                            .buffer =
-                                    renderengine::Buffer{
-                                            .buffer = greenBuffer,
-                                            .usePremultipliedAlpha = true,
-                                    },
-                    },
-            .alpha = 1.0f,
-            .sourceDataspace = dataspace,
-            .whitePointNits = 200.f,
-    };
-
-    std::vector<renderengine::LayerSettings> layers;
-    layers.emplace_back(greenLayer);
-    invokeDraw(display, layers);
-
-    expectBufferColor(Rect(99, 99, 101, 101), 255, 128, 0, 255, 1);
-}
-
 TEST_P(RenderEngineTest, testDimming) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2663,7 +2595,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2741,7 +2673,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2804,7 +2736,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_deviceHandles) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2868,7 +2800,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2922,7 +2854,7 @@
 }
 
 TEST_P(RenderEngineTest, test_isOpaque) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2972,7 +2904,7 @@
 }
 
 TEST_P(RenderEngineTest, test_tonemapPQMatches) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2989,7 +2921,7 @@
 }
 
 TEST_P(RenderEngineTest, test_tonemapHLGMatches) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -3006,7 +2938,7 @@
 }
 
 TEST_P(RenderEngineTest, r8_behaves_as_mask) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -3066,7 +2998,7 @@
 }
 
 TEST_P(RenderEngineTest, r8_respects_color_transform) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -3131,7 +3063,7 @@
 }
 
 TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -3199,12 +3131,19 @@
 }
 
 TEST_P(RenderEngineTest, primeShaderCache) {
-    if (!GetParam()->typeSupported()) {
+    // TODO: b/331447071 - Fix in Graphite and re-enable.
+    if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
+        GTEST_SKIP();
+    }
+
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
 
-    auto fut = mRE->primeCache(false);
+    PrimeCacheConfig config;
+    config.cacheUltraHDR = false;
+    auto fut = mRE->primeCache(config);
     if (fut.valid()) {
         fut.wait();
     }
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 1b9adba..bdd9402 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -25,6 +25,7 @@
 
 namespace android {
 
+using renderengine::PrimeCacheConfig;
 using testing::_;
 using testing::Eq;
 using testing::Mock;
@@ -35,8 +36,7 @@
 
     void SetUp() override {
         mThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
-                [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); },
-                renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED);
+                [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); });
     }
 
     std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE;
@@ -49,9 +49,25 @@
     mThreadedRE->dump(testString);
 }
 
+MATCHER_P(EqConfig, other, "Equality for prime cache config") {
+    return arg.cacheHolePunchLayer == other.cacheHolePunchLayer &&
+            arg.cacheSolidLayers == other.cacheSolidLayers &&
+            arg.cacheSolidDimmedLayers == other.cacheSolidDimmedLayers &&
+            arg.cacheImageLayers == other.cacheImageLayers &&
+            arg.cacheImageDimmedLayers == other.cacheImageDimmedLayers &&
+            arg.cacheClippedLayers == other.cacheClippedLayers &&
+            arg.cacheShadowLayers == other.cacheShadowLayers &&
+            arg.cachePIPImageLayers == other.cachePIPImageLayers &&
+            arg.cacheTransparentImageDimmedLayers == other.cacheTransparentImageDimmedLayers &&
+            arg.cacheClippedDimmedImageLayers == other.cacheClippedDimmedImageLayers &&
+            arg.cacheUltraHDR == other.cacheUltraHDR;
+}
+
 TEST_F(RenderEngineThreadedTest, primeCache) {
-    EXPECT_CALL(*mRenderEngine, primeCache(false));
-    mThreadedRE->primeCache(false);
+    PrimeCacheConfig config;
+    config.cacheUltraHDR = false;
+    EXPECT_CALL(*mRenderEngine, primeCache(EqConfig(config)));
+    mThreadedRE->primeCache(config);
     // need to call ANY synchronous function after primeCache to ensure that primeCache has
     // completed asynchronously before the test completes execution.
     mThreadedRE->getContextPriority();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 367bee8..d27c151 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -33,13 +33,12 @@
 namespace renderengine {
 namespace threaded {
 
-std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory,
-                                                                   RenderEngineType type) {
-    return std::make_unique<RenderEngineThreaded>(std::move(factory), type);
+std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory) {
+    return std::make_unique<RenderEngineThreaded>(std::move(factory));
 }
 
-RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type)
-      : RenderEngine(type) {
+RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory)
+      : RenderEngine(Threaded::YES) {
     ATRACE_CALL();
 
     std::lock_guard lockThread(mThreadMutex);
@@ -125,11 +124,13 @@
 }
 
 void RenderEngineThreaded::waitUntilInitialized() const {
-    std::unique_lock<std::mutex> lock(mInitializedMutex);
-    mInitializedCondition.wait(lock, [=] { return mIsInitialized; });
+    if (!mIsInitialized) {
+        std::unique_lock<std::mutex> lock(mInitializedMutex);
+        mInitializedCondition.wait(lock, [this] { return mIsInitialized.load(); });
+    }
 }
 
-std::future<void> RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) {
+std::future<void> RenderEngineThreaded::primeCache(PrimeCacheConfig config) {
     const auto resultPromise = std::make_shared<std::promise<void>>();
     std::future<void> resultFuture = resultPromise->get_future();
     ATRACE_CALL();
@@ -137,20 +138,19 @@
     // for the futures.
     {
         std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push(
-                [resultPromise, shouldPrimeUltraHDR](renderengine::RenderEngine& instance) {
-                    ATRACE_NAME("REThreaded::primeCache");
-                    if (setSchedFifo(false) != NO_ERROR) {
-                        ALOGW("Couldn't set SCHED_OTHER for primeCache");
-                    }
+        mFunctionCalls.push([resultPromise, config](renderengine::RenderEngine& instance) {
+            ATRACE_NAME("REThreaded::primeCache");
+            if (setSchedFifo(false) != NO_ERROR) {
+                ALOGW("Couldn't set SCHED_OTHER for primeCache");
+            }
 
-                    instance.primeCache(shouldPrimeUltraHDR);
-                    resultPromise->set_value();
+            instance.primeCache(config);
+            resultPromise->set_value();
 
-                    if (setSchedFifo(true) != NO_ERROR) {
-                        ALOGW("Couldn't set SCHED_FIFO for primeCache");
-                    }
-                });
+            if (setSchedFifo(true) != NO_ERROR) {
+                ALOGW("Couldn't set SCHED_FIFO for primeCache");
+            }
+        });
     }
     mCondition.notify_one();
 
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 74af2bd..d4997d6 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -37,12 +37,11 @@
  */
 class RenderEngineThreaded : public RenderEngine {
 public:
-    static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory,
-                                                        RenderEngineType type);
+    static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory);
 
-    RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type);
+    RenderEngineThreaded(CreateInstanceFactory factory);
     ~RenderEngineThreaded() override;
-    std::future<void> primeCache(bool shouldPrimeUltraHDR) override;
+    std::future<void> primeCache(PrimeCacheConfig config) override;
 
     void dump(std::string& result) override;
 
@@ -97,7 +96,7 @@
 
     // Used to allow select thread safe methods to be accessed without requiring the
     // method to be invoked on the RenderEngine thread
-    bool mIsInitialized = false;
+    std::atomic_bool mIsInitialized = false;
     mutable std::mutex mInitializedMutex;
     mutable std::condition_variable mInitializedCondition;
 
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index d992aa5..7fa47b4 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -21,6 +21,19 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "libsensor_flags",
+    package: "com.android.hardware.libsensor.flags",
+    container: "system",
+    srcs: ["libsensor_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libsensor_flags_c_lib",
+    host_supported: true,
+    aconfig_declarations: "libsensor_flags",
+}
+
 cc_library {
     name: "libsensor",
 
@@ -52,6 +65,10 @@
         "android.companion.virtual.virtualdevice_aidl-cpp",
     ],
 
+    static_libs: [
+        "libsensor_flags_c_lib",
+    ],
+
     export_include_dirs: ["include"],
 
     export_shared_lib_headers: [
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index d112a12..9411e20 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -37,6 +37,8 @@
 #include <sensor/Sensor.h>
 #include <sensor/SensorEventQueue.h>
 
+#include <com_android_hardware_libsensor_flags.h>
+
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
@@ -88,49 +90,51 @@
     SensorManager* sensorManager;
     auto iterator = sPackageInstances.find(packageName);
 
+    const uid_t uid = IPCThreadState::self()->getCallingUid();
+    const int deviceId = getDeviceIdForUid(uid);
+
+    // Return the cached instance if the device association of the package has not changed.
     if (iterator != sPackageInstances.end()) {
         sensorManager = iterator->second;
-    } else {
-        String16 opPackageName = packageName;
-        const uid_t uid = IPCThreadState::self()->getCallingUid();
-
-        // It is possible that the calling code has no access to the package name.
-        // In this case we will get the packages for the calling UID and pick the
-        // first one for attributing the app op. This will work correctly for
-        // runtime permissions as for legacy apps we will toggle the app op for
-        // all packages in the UID. The caveat is that the operation may be attributed
-        // to the wrong package and stats based on app ops may be slightly off.
-        if (opPackageName.size() <= 0) {
-            sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
-            if (binder != nullptr) {
-                Vector<String16> packages;
-                interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
-                if (!packages.isEmpty()) {
-                    opPackageName = packages[0];
-                } else {
-                    ALOGE("No packages for calling UID");
-                }
-            } else {
-                ALOGE("Cannot get permission service");
-            }
+        if (sensorManager->mDeviceId == deviceId) {
+            return *sensorManager;
         }
-
-        // Check if the calling UID is observed on a virtual device. If so, provide that device's
-        // sensors by default instead of the default device's sensors.
-        const int deviceId = getDeviceIdForUid(uid);
-        sensorManager = new SensorManager(opPackageName, deviceId);
-
-        // If we had no package name, we looked it up from the UID and the sensor
-        // manager instance we created should also be mapped to the empty package
-        // name, to avoid looking up the packages for a UID and get the same result.
-        if (packageName.size() <= 0) {
-            sPackageInstances.insert(std::make_pair(String16(), sensorManager));
-        }
-
-        // Stash the per package sensor manager.
-        sPackageInstances.insert(std::make_pair(opPackageName, sensorManager));
     }
 
+    // It is possible that the calling code has no access to the package name.
+    // In this case we will get the packages for the calling UID and pick the
+    // first one for attributing the app op. This will work correctly for
+    // runtime permissions as for legacy apps we will toggle the app op for
+    // all packages in the UID. The caveat is that the operation may be attributed
+    // to the wrong package and stats based on app ops may be slightly off.
+    String16 opPackageName = packageName;
+    if (opPackageName.size() <= 0) {
+        sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
+        if (binder != nullptr) {
+            Vector<String16> packages;
+            interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
+            if (!packages.isEmpty()) {
+                opPackageName = packages[0];
+            } else {
+                ALOGE("No packages for calling UID");
+            }
+        } else {
+            ALOGE("Cannot get permission service");
+        }
+    }
+
+    sensorManager = new SensorManager(opPackageName, deviceId);
+
+    // If we had no package name, we looked it up from the UID and the sensor
+    // manager instance we created should also be mapped to the empty package
+    // name, to avoid looking up the packages for a UID and get the same result.
+    if (packageName.size() <= 0) {
+        sPackageInstances.insert(std::make_pair(String16(), sensorManager));
+    }
+
+    // Stash the per package sensor manager.
+    sPackageInstances.insert(std::make_pair(opPackageName, sensorManager));
+
     return *sensorManager;
 }
 
@@ -190,6 +194,9 @@
 }
 
 status_t SensorManager::assertStateLocked() {
+#if COM_ANDROID_HARDWARE_LIBSENSOR_FLAGS(SENSORMANAGER_PING_BINDER)
+    if (mSensorServer == nullptr) {
+#else
     bool initSensorManager = false;
     if (mSensorServer == nullptr) {
         initSensorManager = true;
@@ -201,6 +208,7 @@
         }
     }
     if (initSensorManager) {
+#endif
         waitForSensorService(&mSensorServer);
         LOG_ALWAYS_FATAL_IF(mSensorServer == nullptr, "getService(SensorService) NULL");
 
@@ -248,6 +256,22 @@
     return static_cast<ssize_t>(mSensors.size());
 }
 
+ssize_t SensorManager::getDefaultDeviceSensorList(Vector<Sensor> & list) {
+    Mutex::Autolock _l(mLock);
+    status_t err = assertStateLocked();
+    if (err < 0) {
+        return static_cast<ssize_t>(err);
+    }
+
+    if (mDeviceId == DEVICE_ID_DEFAULT) {
+        list = mSensors;
+    } else {
+        list = mSensorServer->getSensorList(mOpPackageName);
+    }
+
+    return static_cast<ssize_t>(list.size());
+}
+
 ssize_t SensorManager::getDynamicSensorList(Vector<Sensor> & dynamicSensors) {
     Mutex::Autolock _l(mLock);
     status_t err = assertStateLocked();
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index e67fac7..49f050a 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -58,6 +58,7 @@
     ~SensorManager();
 
     ssize_t getSensorList(Sensor const* const** list);
+    ssize_t getDefaultDeviceSensorList(Vector<Sensor> & list);
     ssize_t getDynamicSensorList(Vector<Sensor>& list);
     ssize_t getDynamicSensorList(Sensor const* const** list);
     ssize_t getRuntimeSensorList(int deviceId, Vector<Sensor>& list);
diff --git a/libs/sensor/libsensor_flags.aconfig b/libs/sensor/libsensor_flags.aconfig
new file mode 100644
index 0000000..c511f4a
--- /dev/null
+++ b/libs/sensor/libsensor_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.hardware.libsensor.flags"
+container: "system"
+
+flag {
+  name: "sensormanager_ping_binder"
+  namespace: "sensors"
+  description: "Whether to pingBinder on SensorManager init"
+  bug: "322228259"
+  is_fixed_read_only: true
+}
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index 57c74ee..3f3ad93 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -108,7 +108,7 @@
 
 bool SensorPrivacyManager::isToggleSensorPrivacyEnabled(int sensor)
 {
-	sp<hardware::ISensorPrivacyManager> service = getService();
+    sp<hardware::ISensorPrivacyManager> service = getService();
     if (service != nullptr) {
         bool result;
         service->isCombinedToggleSensorPrivacyEnabled(sensor, &result);
@@ -143,6 +143,38 @@
     return UNKNOWN_ERROR;
 }
 
+int SensorPrivacyManager::getToggleSensorPrivacyState(int toggleType, int sensor)
+{
+    sp<hardware::ISensorPrivacyManager> service = getService();
+    if (service != nullptr) {
+        int result;
+        service->getToggleSensorPrivacyState(toggleType, sensor, &result);
+        return result;
+    }
+    // if the SensorPrivacyManager is not available then assume sensor privacy is disabled
+    return DISABLED;
+}
+
+std::vector<String16> SensorPrivacyManager::getCameraPrivacyAllowlist(){
+    sp<hardware::ISensorPrivacyManager> service = getService();
+    std::vector<String16> result;
+    if (service != nullptr) {
+        service->getCameraPrivacyAllowlist(&result);
+        return result;
+    }
+    return result;
+}
+
+bool SensorPrivacyManager::isCameraPrivacyEnabled(String16 packageName){
+    sp<hardware::ISensorPrivacyManager> service = getService();
+    if (service != nullptr) {
+        bool result;
+        service->isCameraPrivacyEnabled(packageName, &result);
+        return result;
+    }
+    return false;
+}
+
 status_t SensorPrivacyManager::linkToDeath(const sp<IBinder::DeathRecipient>& recipient)
 {
     sp<hardware::ISensorPrivacyManager> service = getService();
diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
index eccd54c..694af00 100644
--- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
+++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
@@ -21,4 +21,5 @@
  */
 oneway interface ISensorPrivacyListener {
     void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled);
+    void onSensorPrivacyStateChanged(int toggleType, int sensor, int state);
 }
diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
index 49a1e1e..f707187 100644
--- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
+++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
@@ -41,4 +41,15 @@
     void setToggleSensorPrivacy(int userId, int source, int sensor, boolean enable);
 
     void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
+
+    List<String> getCameraPrivacyAllowlist();
+
+    int getToggleSensorPrivacyState(int toggleType, int sensor);
+
+    void setToggleSensorPrivacyState(int userId, int source, int sensor, int state);
+
+    void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor, int  state);
+
+    boolean isCameraPrivacyEnabled(String packageName);
+
 }
diff --git a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
index fc5fdf7..8935b76 100644
--- a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
+++ b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
@@ -32,12 +32,20 @@
 public:
     enum {
         TOGGLE_SENSOR_MICROPHONE = 1,
-        TOGGLE_SENSOR_CAMERA = 2
+        TOGGLE_SENSOR_CAMERA = 2,
+        TOGGLE_SENSOR_UNKNOWN = -1
     };
 
     enum {
         TOGGLE_TYPE_SOFTWARE = 1,
-        TOGGLE_TYPE_HARDWARE = 2
+        TOGGLE_TYPE_HARDWARE = 2,
+        TOGGLE_TYPE_UNKNOWN = -1
+    };
+
+    enum {
+        ENABLED = 1,
+        DISABLED = 2,
+        ENABLED_EXCEPT_ALLOWLISTED_APPS = 3
     };
 
     SensorPrivacyManager();
@@ -51,6 +59,9 @@
     bool isToggleSensorPrivacyEnabled(int sensor);
     bool isToggleSensorPrivacyEnabled(int toggleType, int sensor);
     status_t isToggleSensorPrivacyEnabled(int toggleType, int sensor, bool &result);
+    int getToggleSensorPrivacyState(int toggleType, int sensor);
+    std::vector<String16> getCameraPrivacyAllowlist();
+    bool isCameraPrivacyEnabled(String16 packageName);
 
     status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
     status_t unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient);
diff --git a/libs/shaders/Android.bp b/libs/shaders/Android.bp
index 960f845..82fbfda 100644
--- a/libs/shaders/Android.bp
+++ b/libs/shaders/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_library_static {
diff --git a/libs/shaders/tests/Android.bp b/libs/shaders/tests/Android.bp
index 5639d74..2103679 100644
--- a/libs/shaders/tests/Android.bp
+++ b/libs/shaders/tests/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp
index 8c8815d..d569ac3 100644
--- a/libs/tonemap/Android.bp
+++ b/libs/tonemap/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_library_static {
diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp
index 5c5fc6c..79ee7c2 100644
--- a/libs/tonemap/tests/Android.bp
+++ b/libs/tonemap/tests/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
diff --git a/libs/tracing_perfetto/.clang-format b/libs/tracing_perfetto/.clang-format
new file mode 100644
index 0000000..f397454
--- /dev/null
+++ b/libs/tracing_perfetto/.clang-format
@@ -0,0 +1,12 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+ColumnLimit: 80
+ContinuationIndentWidth: 4
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 2
+PointerAlignment: Left
+UseTab: Never
+PenaltyExcessCharacter: 32
\ No newline at end of file
diff --git a/libs/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp
new file mode 100644
index 0000000..3a4c869
--- /dev/null
+++ b/libs/tracing_perfetto/Android.bp
@@ -0,0 +1,49 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_shared {
+    name: "libtracing_perfetto",
+    export_include_dirs: [
+        "include",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-enum-compare",
+        "-Wno-unused-function",
+    ],
+
+    srcs: [
+        "tracing_perfetto.cpp",
+        "tracing_perfetto_internal.cpp",
+    ],
+
+    shared_libs: [
+        "libcutils",
+        "libperfetto_c",
+        "android.os.flags-aconfig-cc-host",
+    ],
+
+    host_supported: true,
+}
diff --git a/libs/tracing_perfetto/OWNERS b/libs/tracing_perfetto/OWNERS
new file mode 100644
index 0000000..e2d4b46
--- /dev/null
+++ b/libs/tracing_perfetto/OWNERS
@@ -0,0 +1,2 @@
+zezeozue@google.com
+biswarupp@google.com
\ No newline at end of file
diff --git a/libs/tracing_perfetto/TEST_MAPPING b/libs/tracing_perfetto/TEST_MAPPING
new file mode 100644
index 0000000..1805e18
--- /dev/null
+++ b/libs/tracing_perfetto/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "libtracing_perfetto_tests"
+    }
+  ]
+}
diff --git a/libs/tracing_perfetto/include/trace_categories.h b/libs/tracing_perfetto/include/trace_categories.h
new file mode 100644
index 0000000..6d4168b
--- /dev/null
+++ b/libs/tracing_perfetto/include/trace_categories.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 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 TRACE_CATEGORIES_H
+#define TRACE_CATEGORIES_H
+
+/**
+ * Keep these in sync with frameworks/base/core/java/android/os/Trace.java.
+ */
+#define TRACE_CATEGORY_ALWAYS (1 << 0)
+#define TRACE_CATEGORY_GRAPHICS (1 << 1)
+#define TRACE_CATEGORY_INPUT (1 << 2)
+#define TRACE_CATEGORY_VIEW (1 << 3)
+#define TRACE_CATEGORY_WEBVIEW (1 << 4)
+#define TRACE_CATEGORY_WINDOW_MANAGER (1 << 5)
+#define TRACE_CATEGORY_ACTIVITY_MANAGER (1 << 6)
+#define TRACE_CATEGORY_SYNC_MANAGER (1 << 7)
+#define TRACE_CATEGORY_AUDIO (1 << 8)
+#define TRACE_CATEGORY_VIDEO (1 << 9)
+#define TRACE_CATEGORY_CAMERA (1 << 10)
+#define TRACE_CATEGORY_HAL (1 << 11)
+#define TRACE_CATEGORY_APP (1 << 12)
+#define TRACE_CATEGORY_RESOURCES (1 << 13)
+#define TRACE_CATEGORY_DALVIK (1 << 14)
+#define TRACE_CATEGORY_RS (1 << 15)
+#define TRACE_CATEGORY_BIONIC (1 << 16)
+#define TRACE_CATEGORY_POWER (1 << 17)
+#define TRACE_CATEGORY_PACKAGE_MANAGER (1 << 18)
+#define TRACE_CATEGORY_SYSTEM_SERVER (1 << 19)
+#define TRACE_CATEGORY_DATABASE (1 << 20)
+#define TRACE_CATEGORY_NETWORK (1 << 21)
+#define TRACE_CATEGORY_ADB (1 << 22)
+#define TRACE_CATEGORY_VIBRATOR (1 << 23)
+#define TRACE_CATEGORY_AIDL (1 << 24)
+#define TRACE_CATEGORY_NNAPI (1 << 25)
+#define TRACE_CATEGORY_RRO (1 << 26)
+#define TRACE_CATEGORY_THERMAL (1 << 27)
+
+// Allow all categories except TRACE_CATEGORY_APP
+#define TRACE_CATEGORIES                                                      \
+  TRACE_CATEGORY_ALWAYS | TRACE_CATEGORY_GRAPHICS | TRACE_CATEGORY_INPUT |    \
+      TRACE_CATEGORY_VIEW | TRACE_CATEGORY_WEBVIEW |                          \
+      TRACE_CATEGORY_WINDOW_MANAGER | TRACE_CATEGORY_ACTIVITY_MANAGER |       \
+      TRACE_CATEGORY_SYNC_MANAGER | TRACE_CATEGORY_AUDIO |                    \
+      TRACE_CATEGORY_VIDEO | TRACE_CATEGORY_CAMERA | TRACE_CATEGORY_HAL |     \
+      TRACE_CATEGORY_RESOURCES | TRACE_CATEGORY_DALVIK | TRACE_CATEGORY_RS |  \
+      TRACE_CATEGORY_BIONIC | TRACE_CATEGORY_POWER |                          \
+      TRACE_CATEGORY_PACKAGE_MANAGER | TRACE_CATEGORY_SYSTEM_SERVER |         \
+      TRACE_CATEGORY_DATABASE | TRACE_CATEGORY_NETWORK | TRACE_CATEGORY_ADB | \
+      TRACE_CATEGORY_VIBRATOR | TRACE_CATEGORY_AIDL | TRACE_CATEGORY_NNAPI |  \
+      TRACE_CATEGORY_RRO | TRACE_CATEGORY_THERMAL
+#endif  // TRACE_CATEGORIES_H
diff --git a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl b/libs/tracing_perfetto/include/trace_result.h
similarity index 71%
copy from libs/gui/aidl/android/gui/LayerDebugInfo.aidl
copy to libs/tracing_perfetto/include/trace_result.h
index faca980..f7581fc 100644
--- a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl
+++ b/libs/tracing_perfetto/include/trace_result.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,17 @@
  * limitations under the License.
  */
 
-package android.gui;
+#ifndef TRACE_RESULT_H
+#define TRACE_RESULT_H
 
-parcelable LayerDebugInfo cpp_header "gui/LayerDebugInfo.h";
+namespace tracing_perfetto {
+
+enum class Result {
+  SUCCESS,
+  NOT_SUPPORTED,
+  INVALID_INPUT,
+};
+
+}
+
+#endif  // TRACE_RESULT_H
diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h
new file mode 100644
index 0000000..2c1c2a4
--- /dev/null
+++ b/libs/tracing_perfetto/include/tracing_perfetto.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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 TRACING_PERFETTO_H
+#define TRACING_PERFETTO_H
+
+#include <stdint.h>
+
+#include "trace_result.h"
+
+namespace tracing_perfetto {
+
+void registerWithPerfetto(bool test = false);
+
+Result traceBegin(uint64_t category, const char* name);
+
+Result traceEnd(uint64_t category);
+
+Result traceAsyncBegin(uint64_t category, const char* name, int32_t cookie);
+
+Result traceAsyncEnd(uint64_t category, const char* name, int32_t cookie);
+
+Result traceAsyncBeginForTrack(uint64_t category, const char* name,
+                               const char* trackName, int32_t cookie);
+
+Result traceAsyncEndForTrack(uint64_t category, const char* trackName,
+                             int32_t cookie);
+
+Result traceInstant(uint64_t category, const char* name);
+
+Result traceInstantForTrack(uint64_t category, const char* trackName,
+                            const char* name);
+
+Result traceCounter(uint64_t category, const char* name, int64_t value);
+
+bool isTagEnabled(uint64_t category);
+
+}  // namespace tracing_perfetto
+
+#endif  // TRACING_PERFETTO_H
diff --git a/libs/tracing_perfetto/tests/Android.bp b/libs/tracing_perfetto/tests/Android.bp
new file mode 100644
index 0000000..a35b0e0
--- /dev/null
+++ b/libs/tracing_perfetto/tests/Android.bp
@@ -0,0 +1,45 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_test {
+    name: "libtracing_perfetto_tests",
+    static_libs: [
+        "libflagtest",
+        "libgmock",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "android.os.flags-aconfig-cc-host",
+        "libbase",
+        "libperfetto_c",
+        "libtracing_perfetto",
+    ],
+    srcs: [
+        "tracing_perfetto_test.cpp",
+        "utils.cpp",
+    ],
+    test_suites: ["device-tests"],
+}
diff --git a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
new file mode 100644
index 0000000..7716b9a
--- /dev/null
+++ b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2024 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 "tracing_perfetto.h"
+
+#include <thread>
+
+#include <android_os.h>
+#include <flag_macros.h>
+
+#include "gtest/gtest.h"
+#include "perfetto/public/abi/data_source_abi.h"
+#include "perfetto/public/abi/heap_buffer.h"
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/abi/tracing_session_abi.h"
+#include "perfetto/public/abi/track_event_abi.h"
+#include "perfetto/public/data_source.h"
+#include "perfetto/public/pb_decoder.h"
+#include "perfetto/public/producer.h"
+#include "perfetto/public/protos/config/trace_config.pzc.h"
+#include "perfetto/public/protos/trace/interned_data/interned_data.pzc.h"
+#include "perfetto/public/protos/trace/test_event.pzc.h"
+#include "perfetto/public/protos/trace/trace.pzc.h"
+#include "perfetto/public/protos/trace/trace_packet.pzc.h"
+#include "perfetto/public/protos/trace/track_event/debug_annotation.pzc.h"
+#include "perfetto/public/protos/trace/track_event/track_descriptor.pzc.h"
+#include "perfetto/public/protos/trace/track_event/track_event.pzc.h"
+#include "perfetto/public/protos/trace/trigger.pzc.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+#include "trace_categories.h"
+#include "utils.h"
+
+namespace tracing_perfetto {
+
+using ::perfetto::shlib::test_utils::AllFieldsWithId;
+using ::perfetto::shlib::test_utils::FieldView;
+using ::perfetto::shlib::test_utils::IdFieldView;
+using ::perfetto::shlib::test_utils::MsgField;
+using ::perfetto::shlib::test_utils::PbField;
+using ::perfetto::shlib::test_utils::StringField;
+using ::perfetto::shlib::test_utils::TracingSession;
+using ::perfetto::shlib::test_utils::VarIntField;
+using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
+const auto PERFETTO_SDK_TRACING = ACONFIG_FLAG(android::os, perfetto_sdk_tracing);
+
+class TracingPerfettoTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    tracing_perfetto::registerWithPerfetto(true /* test */);
+  }
+};
+
+// TODO(b/303199244): Add tests for all the library functions.
+
+TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstant,
+                  REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) {
+  TracingSession tracing_session =
+      TracingSession::Builder().set_data_source_name("track_event").Build();
+  tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, "");
+
+  tracing_session.StopBlocking();
+  std::vector<uint8_t> data = tracing_session.ReadBlocking();
+  bool found = false;
+  for (struct PerfettoPbDecoderField trace_field : FieldView(data)) {
+    ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number,
+                                     MsgField(_)));
+    IdFieldView track_event(
+        trace_field, perfetto_protos_TracePacket_track_event_field_number);
+    if (track_event.size() == 0) {
+      continue;
+    }
+    found = true;
+    IdFieldView cat_iid_fields(
+        track_event.front(),
+        perfetto_protos_TrackEvent_category_iids_field_number);
+    ASSERT_THAT(cat_iid_fields, ElementsAre(VarIntField(_)));
+    uint64_t cat_iid = cat_iid_fields.front().value.integer64;
+    EXPECT_THAT(
+        trace_field,
+        AllFieldsWithId(
+            perfetto_protos_TracePacket_interned_data_field_number,
+            ElementsAre(AllFieldsWithId(
+                perfetto_protos_InternedData_event_categories_field_number,
+                ElementsAre(MsgField(UnorderedElementsAre(
+                    PbField(perfetto_protos_EventCategory_iid_field_number,
+                            VarIntField(cat_iid)),
+                    PbField(perfetto_protos_EventCategory_name_field_number,
+                            StringField("input")))))))));
+  }
+  EXPECT_TRUE(found);
+}
+
+}  // namespace tracing_perfetto
\ No newline at end of file
diff --git a/libs/tracing_perfetto/tests/utils.cpp b/libs/tracing_perfetto/tests/utils.cpp
new file mode 100644
index 0000000..9c42028
--- /dev/null
+++ b/libs/tracing_perfetto/tests/utils.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Copied from //external/perfetto/src/shared_lib/test/utils.cc
+
+#include "utils.h"
+
+#include "perfetto/public/abi/heap_buffer.h"
+#include "perfetto/public/pb_msg.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/protos/config/data_source_config.pzc.h"
+#include "perfetto/public/protos/config/trace_config.pzc.h"
+#include "perfetto/public/protos/config/track_event/track_event_config.pzc.h"
+#include "perfetto/public/tracing_session.h"
+
+namespace perfetto {
+namespace shlib {
+namespace test_utils {
+namespace {
+
+std::string ToHexChars(uint8_t val) {
+  std::string ret;
+  uint8_t high_nibble = (val & 0xF0) >> 4;
+  uint8_t low_nibble = (val & 0xF);
+  static const char hex_chars[] = "0123456789ABCDEF";
+  ret.push_back(hex_chars[high_nibble]);
+  ret.push_back(hex_chars[low_nibble]);
+  return ret;
+}
+
+}  // namespace
+
+TracingSession TracingSession::Builder::Build() {
+  struct PerfettoPbMsgWriter writer;
+  struct PerfettoHeapBuffer* hb = PerfettoHeapBufferCreate(&writer.writer);
+
+  struct perfetto_protos_TraceConfig cfg;
+  PerfettoPbMsgInit(&cfg.msg, &writer);
+
+  {
+    struct perfetto_protos_TraceConfig_BufferConfig buffers;
+    perfetto_protos_TraceConfig_begin_buffers(&cfg, &buffers);
+
+    perfetto_protos_TraceConfig_BufferConfig_set_size_kb(&buffers, 1024);
+
+    perfetto_protos_TraceConfig_end_buffers(&cfg, &buffers);
+  }
+
+  {
+    struct perfetto_protos_TraceConfig_DataSource data_sources;
+    perfetto_protos_TraceConfig_begin_data_sources(&cfg, &data_sources);
+
+    {
+      struct perfetto_protos_DataSourceConfig ds_cfg;
+      perfetto_protos_TraceConfig_DataSource_begin_config(&data_sources,
+                                                          &ds_cfg);
+
+      perfetto_protos_DataSourceConfig_set_cstr_name(&ds_cfg,
+                                                     data_source_name_.c_str());
+      if (!enabled_categories_.empty() && !disabled_categories_.empty()) {
+        perfetto_protos_TrackEventConfig te_cfg;
+        perfetto_protos_DataSourceConfig_begin_track_event_config(&ds_cfg,
+                                                                  &te_cfg);
+        for (const std::string& cat : enabled_categories_) {
+          perfetto_protos_TrackEventConfig_set_enabled_categories(
+              &te_cfg, cat.data(), cat.size());
+        }
+        for (const std::string& cat : disabled_categories_) {
+          perfetto_protos_TrackEventConfig_set_disabled_categories(
+              &te_cfg, cat.data(), cat.size());
+        }
+        perfetto_protos_DataSourceConfig_end_track_event_config(&ds_cfg,
+                                                                &te_cfg);
+      }
+
+      perfetto_protos_TraceConfig_DataSource_end_config(&data_sources, &ds_cfg);
+    }
+
+    perfetto_protos_TraceConfig_end_data_sources(&cfg, &data_sources);
+  }
+  size_t cfg_size = PerfettoStreamWriterGetWrittenSize(&writer.writer);
+  std::unique_ptr<uint8_t[]> ser(new uint8_t[cfg_size]);
+  PerfettoHeapBufferCopyInto(hb, &writer.writer, ser.get(), cfg_size);
+  PerfettoHeapBufferDestroy(hb, &writer.writer);
+
+  struct PerfettoTracingSessionImpl* ts =
+      PerfettoTracingSessionCreate(PERFETTO_BACKEND_IN_PROCESS);
+
+  PerfettoTracingSessionSetup(ts, ser.get(), cfg_size);
+
+  PerfettoTracingSessionStartBlocking(ts);
+
+  return TracingSession::Adopt(ts);
+}
+
+TracingSession TracingSession::Adopt(struct PerfettoTracingSessionImpl* session) {
+  TracingSession ret;
+  ret.session_ = session;
+  ret.stopped_ = std::make_unique<WaitableEvent>();
+  PerfettoTracingSessionSetStopCb(
+      ret.session_,
+      [](struct PerfettoTracingSessionImpl*, void* arg) {
+        static_cast<WaitableEvent*>(arg)->Notify();
+      },
+      ret.stopped_.get());
+  return ret;
+}
+
+TracingSession::TracingSession(TracingSession&& other) noexcept {
+  session_ = other.session_;
+  other.session_ = nullptr;
+  stopped_ = std::move(other.stopped_);
+  other.stopped_ = nullptr;
+}
+
+TracingSession::~TracingSession() {
+  if (!session_) {
+    return;
+  }
+  if (!stopped_->IsNotified()) {
+    PerfettoTracingSessionStopBlocking(session_);
+    stopped_->WaitForNotification();
+  }
+  PerfettoTracingSessionDestroy(session_);
+}
+
+bool TracingSession::FlushBlocking(uint32_t timeout_ms) {
+  WaitableEvent notification;
+  bool result;
+  auto* cb = new std::function<void(bool)>([&](bool success) {
+    result = success;
+    notification.Notify();
+  });
+  PerfettoTracingSessionFlushAsync(
+      session_, timeout_ms,
+      [](PerfettoTracingSessionImpl*, bool success, void* user_arg) {
+        auto* f = reinterpret_cast<std::function<void(bool)>*>(user_arg);
+        (*f)(success);
+        delete f;
+      },
+      cb);
+  notification.WaitForNotification();
+  return result;
+}
+
+void TracingSession::WaitForStopped() {
+  stopped_->WaitForNotification();
+}
+
+void TracingSession::StopBlocking() {
+  PerfettoTracingSessionStopBlocking(session_);
+}
+
+std::vector<uint8_t> TracingSession::ReadBlocking() {
+  std::vector<uint8_t> data;
+  PerfettoTracingSessionReadTraceBlocking(
+      session_,
+      [](struct PerfettoTracingSessionImpl*, const void* trace_data,
+         size_t size, bool, void* user_arg) {
+        auto& dst = *static_cast<std::vector<uint8_t>*>(user_arg);
+        auto* src = static_cast<const uint8_t*>(trace_data);
+        dst.insert(dst.end(), src, src + size);
+      },
+      &data);
+  return data;
+}
+
+}  // namespace test_utils
+}  // namespace shlib
+}  // namespace perfetto
+
+void PrintTo(const PerfettoPbDecoderField& field, std::ostream* pos) {
+  std::ostream& os = *pos;
+  PerfettoPbDecoderStatus status =
+      static_cast<PerfettoPbDecoderStatus>(field.status);
+  switch (status) {
+    case PERFETTO_PB_DECODER_ERROR:
+      os << "MALFORMED PROTOBUF";
+      break;
+    case PERFETTO_PB_DECODER_DONE:
+      os << "DECODER DONE";
+      break;
+    case PERFETTO_PB_DECODER_OK:
+      switch (field.wire_type) {
+        case PERFETTO_PB_WIRE_TYPE_DELIMITED:
+          os << "\"";
+          for (size_t i = 0; i < field.value.delimited.len; i++) {
+            os << perfetto::shlib::test_utils::ToHexChars(
+                      field.value.delimited.start[i])
+               << " ";
+          }
+          os << "\"";
+          break;
+        case PERFETTO_PB_WIRE_TYPE_VARINT:
+          os << "varint: " << field.value.integer64;
+          break;
+        case PERFETTO_PB_WIRE_TYPE_FIXED32:
+          os << "fixed32: " << field.value.integer32;
+          break;
+        case PERFETTO_PB_WIRE_TYPE_FIXED64:
+          os << "fixed64: " << field.value.integer64;
+          break;
+      }
+      break;
+  }
+}
diff --git a/libs/tracing_perfetto/tests/utils.h b/libs/tracing_perfetto/tests/utils.h
new file mode 100644
index 0000000..4353554
--- /dev/null
+++ b/libs/tracing_perfetto/tests/utils.h
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Copied from //external/perfetto/src/shared_lib/test/utils.h
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <cassert>
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock-more-matchers.h"
+#include "gtest/gtest-matchers.h"
+#include "gtest/gtest.h"
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/tracing_session.h"
+
+// Pretty printer for gtest
+void PrintTo(const PerfettoPbDecoderField& field, std::ostream*);
+
+namespace perfetto {
+namespace shlib {
+namespace test_utils {
+
+class WaitableEvent {
+ public:
+  WaitableEvent() = default;
+  void Notify() {
+    std::unique_lock<std::mutex> lock(m_);
+    notified_ = true;
+    cv_.notify_one();
+  }
+  bool WaitForNotification() {
+    std::unique_lock<std::mutex> lock(m_);
+    cv_.wait(lock, [this] { return notified_; });
+    return notified_;
+  }
+  bool IsNotified() {
+    std::unique_lock<std::mutex> lock(m_);
+    return notified_;
+  }
+
+ private:
+  std::mutex m_;
+  std::condition_variable cv_;
+  bool notified_ = false;
+};
+
+class TracingSession {
+ public:
+  class Builder {
+   public:
+    Builder() = default;
+    Builder& set_data_source_name(std::string data_source_name) {
+      data_source_name_ = std::move(data_source_name);
+      return *this;
+    }
+    Builder& add_enabled_category(std::string category) {
+      enabled_categories_.push_back(std::move(category));
+      return *this;
+    }
+    Builder& add_disabled_category(std::string category) {
+      disabled_categories_.push_back(std::move(category));
+      return *this;
+    }
+    TracingSession Build();
+
+   private:
+    std::string data_source_name_;
+    std::vector<std::string> enabled_categories_;
+    std::vector<std::string> disabled_categories_;
+  };
+
+  static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
+
+  TracingSession(TracingSession&&) noexcept;
+
+  ~TracingSession();
+
+  struct PerfettoTracingSessionImpl* session() const {
+    return session_;
+  }
+
+  bool FlushBlocking(uint32_t timeout_ms);
+  void WaitForStopped();
+  void StopBlocking();
+  std::vector<uint8_t> ReadBlocking();
+
+ private:
+  TracingSession() = default;
+  struct PerfettoTracingSessionImpl* session_;
+  std::unique_ptr<WaitableEvent> stopped_;
+};
+
+template <typename FieldSkipper>
+class FieldViewBase {
+ public:
+  class Iterator {
+   public:
+    using iterator_category = std::input_iterator_tag;
+    using value_type = const PerfettoPbDecoderField;
+    using pointer = value_type;
+    using reference = value_type;
+    reference operator*() const {
+      struct PerfettoPbDecoder decoder;
+      decoder.read_ptr = read_ptr_;
+      decoder.end_ptr = end_ptr_;
+      struct PerfettoPbDecoderField field;
+      do {
+        field = PerfettoPbDecoderParseField(&decoder);
+      } while (field.status == PERFETTO_PB_DECODER_OK &&
+               skipper_.ShouldSkip(field));
+      return field;
+    }
+    Iterator& operator++() {
+      struct PerfettoPbDecoder decoder;
+      decoder.read_ptr = read_ptr_;
+      decoder.end_ptr = end_ptr_;
+      PerfettoPbDecoderSkipField(&decoder);
+      read_ptr_ = decoder.read_ptr;
+      AdvanceToFirstInterestingField();
+      return *this;
+    }
+    Iterator operator++(int) {
+      Iterator tmp = *this;
+      ++(*this);
+      return tmp;
+    }
+
+    friend bool operator==(const Iterator& a, const Iterator& b) {
+      return a.read_ptr_ == b.read_ptr_;
+    }
+    friend bool operator!=(const Iterator& a, const Iterator& b) {
+      return a.read_ptr_ != b.read_ptr_;
+    }
+
+   private:
+    Iterator(const uint8_t* read_ptr, const uint8_t* end_ptr,
+             const FieldSkipper& skipper)
+        : read_ptr_(read_ptr), end_ptr_(end_ptr), skipper_(skipper) {
+      AdvanceToFirstInterestingField();
+    }
+    void AdvanceToFirstInterestingField() {
+      struct PerfettoPbDecoder decoder;
+      decoder.read_ptr = read_ptr_;
+      decoder.end_ptr = end_ptr_;
+      struct PerfettoPbDecoderField field;
+      const uint8_t* prev_read_ptr;
+      do {
+        prev_read_ptr = decoder.read_ptr;
+        field = PerfettoPbDecoderParseField(&decoder);
+      } while (field.status == PERFETTO_PB_DECODER_OK &&
+               skipper_.ShouldSkip(field));
+      if (field.status == PERFETTO_PB_DECODER_OK) {
+        read_ptr_ = prev_read_ptr;
+      } else {
+        read_ptr_ = decoder.read_ptr;
+      }
+    }
+    friend class FieldViewBase<FieldSkipper>;
+    const uint8_t* read_ptr_;
+    const uint8_t* end_ptr_;
+    const FieldSkipper& skipper_;
+  };
+  using value_type = const PerfettoPbDecoderField;
+  using const_iterator = Iterator;
+  template <typename... Args>
+  explicit FieldViewBase(const uint8_t* begin, const uint8_t* end, Args... args)
+      : begin_(begin), end_(end), s_(args...) {
+  }
+  template <typename... Args>
+  explicit FieldViewBase(const std::vector<uint8_t>& data, Args... args)
+      : FieldViewBase(data.data(), data.data() + data.size(), args...) {
+  }
+  template <typename... Args>
+  explicit FieldViewBase(const struct PerfettoPbDecoderField& field,
+                         Args... args)
+      : s_(args...) {
+    if (field.wire_type != PERFETTO_PB_WIRE_TYPE_DELIMITED) {
+      abort();
+    }
+    begin_ = field.value.delimited.start;
+    end_ = begin_ + field.value.delimited.len;
+  }
+  Iterator begin() const {
+    return Iterator(begin_, end_, s_);
+  }
+  Iterator end() const {
+    return Iterator(end_, end_, s_);
+  }
+  PerfettoPbDecoderField front() const {
+    return *begin();
+  }
+
+  size_t size() const {
+    size_t count = 0;
+    for (auto field : *this) {
+      (void)field;
+      count++;
+    }
+    return count;
+  }
+
+  bool ok() const {
+    for (auto field : *this) {
+      if (field.status != PERFETTO_PB_DECODER_OK) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
+  const uint8_t* begin_;
+  const uint8_t* end_;
+  FieldSkipper s_;
+};
+
+// Pretty printer for gtest
+template <typename FieldSkipper>
+void PrintTo(const FieldViewBase<FieldSkipper>& field_view, std::ostream* pos) {
+  std::ostream& os = *pos;
+  os << "{";
+  for (PerfettoPbDecoderField f : field_view) {
+    PrintTo(f, pos);
+    os << ", ";
+  }
+  os << "}";
+}
+
+class IdFieldSkipper {
+ public:
+  explicit IdFieldSkipper(uint32_t id) : id_(id) {
+  }
+  explicit IdFieldSkipper(int32_t id) : id_(static_cast<uint32_t>(id)) {
+  }
+  bool ShouldSkip(const struct PerfettoPbDecoderField& field) const {
+    return field.id != id_;
+  }
+
+ private:
+  uint32_t id_;
+};
+
+class NoFieldSkipper {
+ public:
+  NoFieldSkipper() = default;
+  bool ShouldSkip(const struct PerfettoPbDecoderField&) const {
+    return false;
+  }
+};
+
+// View over all the fields of a contiguous serialized protobuf message.
+//
+// Examples:
+//
+// for (struct PerfettoPbDecoderField field : FieldView(msg_begin, msg_end)) {
+//   //...
+// }
+// FieldView fields2(/*PerfettoPbDecoderField*/ nested_field);
+// FieldView fields3(/*std::vector<uint8_t>*/ data);
+// size_t num = fields1.size(); // The number of fields.
+// bool ok = fields1.ok(); // Checks that the message is not malformed.
+using FieldView = FieldViewBase<NoFieldSkipper>;
+
+// Like `FieldView`, but only considers fields with a specific id.
+//
+// Examples:
+//
+// IdFieldView fields(msg_begin, msg_end, id)
+using IdFieldView = FieldViewBase<IdFieldSkipper>;
+
+// Matches a PerfettoPbDecoderField with the specified id. Accepts another
+// matcher to match the contents of the field.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, PbField(900, VarIntField(5)));
+template <typename M>
+auto PbField(int32_t id, M m) {
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::id, id), m);
+}
+
+// Matches a PerfettoPbDecoderField submessage field. Accepts a container
+// matcher for the subfields.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, MsgField(ElementsAre(...)));
+template <typename M>
+auto MsgField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) { return FieldView(field); };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField length delimited field. Accepts a string
+// matcher.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, StringField("string"));
+template <typename M>
+auto StringField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return std::string(
+        reinterpret_cast<const char*>(field.value.delimited.start),
+        field.value.delimited.len);
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField VarInt field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, VarIntField(1)));
+template <typename M>
+auto VarIntField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.integer64;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_VARINT),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField fixed64 field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, Fixed64Field(1)));
+template <typename M>
+auto Fixed64Field(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.integer64;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_FIXED64),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField fixed32 field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, Fixed32Field(1)));
+template <typename M>
+auto Fixed32Field(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.integer32;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_FIXED32),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField double field. Accepts a double matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, DoubleField(1.0)));
+template <typename M>
+auto DoubleField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.double_val;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_FIXED64),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField float field. Accepts a float matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, FloatField(1.0)));
+template <typename M>
+auto FloatField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.float_val;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_FIXED32),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField submessage field. Accepts a container
+// matcher for the subfields.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, AllFieldsWithId(900, ElementsAre(...)));
+template <typename M>
+auto AllFieldsWithId(int32_t id, M m) {
+  auto f = [id](const PerfettoPbDecoderField& field) {
+    return IdFieldView(field, id);
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
+      testing::ResultOf(f, m));
+}
+
+}  // namespace test_utils
+}  // namespace shlib
+}  // namespace perfetto
+
+#endif  // UTILS_H
diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp
new file mode 100644
index 0000000..6f716ee
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2024 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 "tracing_perfetto.h"
+
+#include <cutils/trace.h>
+
+#include "perfetto/public/te_category_macros.h"
+#include "trace_categories.h"
+#include "tracing_perfetto_internal.h"
+
+namespace tracing_perfetto {
+
+void registerWithPerfetto(bool test) {
+  internal::registerWithPerfetto(test);
+}
+
+Result traceBegin(uint64_t category, const char* name) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceBegin(*perfettoTeCategory, name);
+  } else {
+    atrace_begin(category, name);
+    return Result::SUCCESS;
+  }
+}
+
+Result traceEnd(uint64_t category) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceEnd(*perfettoTeCategory);
+  } else {
+    atrace_end(category);
+    return Result::SUCCESS;
+  }
+}
+
+Result traceAsyncBegin(uint64_t category, const char* name, int32_t cookie) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceAsyncBegin(*perfettoTeCategory, name, cookie);
+  } else {
+    atrace_async_begin(category, name, cookie);
+    return Result::SUCCESS;
+  }
+}
+
+Result traceAsyncEnd(uint64_t category, const char* name, int32_t cookie) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceAsyncEnd(*perfettoTeCategory, name, cookie);
+  } else {
+    atrace_async_end(category, name, cookie);
+    return Result::SUCCESS;
+  }
+}
+
+Result traceAsyncBeginForTrack(uint64_t category, const char* name,
+                               const char* trackName, int32_t cookie) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name, trackName, cookie);
+  } else {
+    atrace_async_for_track_begin(category, trackName, name, cookie);
+    return Result::SUCCESS;
+  }
+}
+
+Result traceAsyncEndForTrack(uint64_t category, const char* trackName,
+                             int32_t cookie) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName, cookie);
+  } else {
+    atrace_async_for_track_end(category, trackName, cookie);
+    return Result::SUCCESS;
+  }
+}
+
+Result traceInstant(uint64_t category, const char* name) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceInstant(*perfettoTeCategory, name);
+  } else {
+    atrace_instant(category, name);
+    return Result::SUCCESS;
+  }
+}
+
+Result traceInstantForTrack(uint64_t category, const char* trackName,
+                            const char* name) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceInstantForTrack(*perfettoTeCategory, trackName, name);
+  } else {
+    atrace_instant_for_track(category, trackName, name);
+    return Result::SUCCESS;
+  }
+}
+
+Result traceCounter(uint64_t category, const char* name, int64_t value) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return internal::perfettoTraceCounter(*perfettoTeCategory, name, value);
+  } else {
+    atrace_int64(category, name, value);
+    return Result::SUCCESS;
+  }
+}
+
+bool isTagEnabled(uint64_t category) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return true;
+  } else {
+    return (atrace_get_enabled_tags() & category) != 0;
+  }
+}
+
+}  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
new file mode 100644
index 0000000..758ace6
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define FRAMEWORK_CATEGORIES(C)                                  \
+  C(always, "always", "Always category")                         \
+  C(graphics, "graphics", "Graphics category")                   \
+  C(input, "input", "Input category")                            \
+  C(view, "view", "View category")                               \
+  C(webview, "webview", "WebView category")                      \
+  C(windowmanager, "wm", "WindowManager category")               \
+  C(activitymanager, "am", "ActivityManager category")           \
+  C(syncmanager, "syncmanager", "SyncManager category")          \
+  C(audio, "audio", "Audio category")                            \
+  C(video, "video", "Video category")                            \
+  C(camera, "camera", "Camera category")                         \
+  C(hal, "hal", "HAL category")                                  \
+  C(app, "app", "App category")                                  \
+  C(resources, "res", "Resources category")                      \
+  C(dalvik, "dalvik", "Dalvik category")                         \
+  C(rs, "rs", "RS category")                                     \
+  C(bionic, "bionic", "Bionic category")                         \
+  C(power, "power", "Power category")                            \
+  C(packagemanager, "packagemanager", "PackageManager category") \
+  C(systemserver, "ss", "System Server category")                \
+  C(database, "database", "Database category")                   \
+  C(network, "network", "Network category")                      \
+  C(adb, "adb", "ADB category")                                  \
+  C(vibrator, "vibrator", "Vibrator category")                   \
+  C(aidl, "aidl", "AIDL category")                               \
+  C(nnapi, "nnapi", "NNAPI category")                            \
+  C(rro, "rro", "RRO category")                                  \
+  C(thermal, "thermal", "Thermal category")
+
+#include "tracing_perfetto_internal.h"
+
+#include <inttypes.h>
+
+#include <mutex>
+
+#include <android_os.h>
+
+#include "perfetto/public/compiler.h"
+#include "perfetto/public/producer.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+#include "trace_categories.h"
+#include "trace_result.h"
+
+namespace tracing_perfetto {
+
+namespace internal {
+
+namespace {
+
+PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES);
+
+PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES);
+
+std::atomic_bool is_perfetto_registered = false;
+
+struct PerfettoTeCategory* toCategory(uint64_t inCategory) {
+  switch (inCategory) {
+    case TRACE_CATEGORY_ALWAYS:
+      return &always;
+    case TRACE_CATEGORY_GRAPHICS:
+      return &graphics;
+    case TRACE_CATEGORY_INPUT:
+      return &input;
+    case TRACE_CATEGORY_VIEW:
+      return &view;
+    case TRACE_CATEGORY_WEBVIEW:
+      return &webview;
+    case TRACE_CATEGORY_WINDOW_MANAGER:
+      return &windowmanager;
+    case TRACE_CATEGORY_ACTIVITY_MANAGER:
+      return &activitymanager;
+    case TRACE_CATEGORY_SYNC_MANAGER:
+      return &syncmanager;
+    case TRACE_CATEGORY_AUDIO:
+      return &audio;
+    case TRACE_CATEGORY_VIDEO:
+      return &video;
+    case TRACE_CATEGORY_CAMERA:
+      return &camera;
+    case TRACE_CATEGORY_HAL:
+      return &hal;
+    case TRACE_CATEGORY_APP:
+      return &app;
+    case TRACE_CATEGORY_RESOURCES:
+      return &resources;
+    case TRACE_CATEGORY_DALVIK:
+      return &dalvik;
+    case TRACE_CATEGORY_RS:
+      return &rs;
+    case TRACE_CATEGORY_BIONIC:
+      return &bionic;
+    case TRACE_CATEGORY_POWER:
+      return &power;
+    case TRACE_CATEGORY_PACKAGE_MANAGER:
+      return &packagemanager;
+    case TRACE_CATEGORY_SYSTEM_SERVER:
+      return &systemserver;
+    case TRACE_CATEGORY_DATABASE:
+      return &database;
+    case TRACE_CATEGORY_NETWORK:
+      return &network;
+    case TRACE_CATEGORY_ADB:
+      return &adb;
+    case TRACE_CATEGORY_VIBRATOR:
+      return &vibrator;
+    case TRACE_CATEGORY_AIDL:
+      return &aidl;
+    case TRACE_CATEGORY_NNAPI:
+      return &nnapi;
+    case TRACE_CATEGORY_RRO:
+      return &rro;
+    case TRACE_CATEGORY_THERMAL:
+      return &thermal;
+    default:
+      return nullptr;
+  }
+}
+
+}  // namespace
+
+bool isPerfettoRegistered() {
+  return is_perfetto_registered;
+}
+
+struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) {
+  struct PerfettoTeCategory* perfettoCategory = toCategory(category);
+  if (perfettoCategory == nullptr) {
+    return nullptr;
+  }
+
+  bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
+      (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED));
+  return enabled ? perfettoCategory : nullptr;
+}
+
+void registerWithPerfetto(bool test) {
+  if (!android::os::perfetto_sdk_tracing()) {
+    return;
+  }
+
+  static std::once_flag registration;
+  std::call_once(registration, [test]() {
+    struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
+    args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM;
+    PerfettoProducerInit(args);
+    PerfettoTeInit();
+    PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES);
+    is_perfetto_registered = true;
+  });
+}
+
+Result perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name) {
+  PERFETTO_TE(category, PERFETTO_TE_SLICE_BEGIN(name));
+  return Result::SUCCESS;
+}
+
+Result perfettoTraceEnd(const struct PerfettoTeCategory& category) {
+  PERFETTO_TE(category, PERFETTO_TE_SLICE_END());
+  return Result::SUCCESS;
+}
+
+Result perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
+                                       const char* trackName, uint64_t cookie) {
+  PERFETTO_TE(
+      category, PERFETTO_TE_SLICE_BEGIN(name),
+      PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid()));
+  return Result::SUCCESS;
+}
+
+Result perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
+                                     const char* trackName, uint64_t cookie) {
+  PERFETTO_TE(
+      category, PERFETTO_TE_SLICE_END(),
+      PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid()));
+  return Result::SUCCESS;
+}
+
+Result perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
+                               uint64_t cookie) {
+  return perfettoTraceAsyncBeginForTrack(category, name, name, cookie);
+}
+
+Result perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
+                             uint64_t cookie) {
+  return perfettoTraceAsyncEndForTrack(category, name, cookie);
+}
+
+Result perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name) {
+  PERFETTO_TE(category, PERFETTO_TE_INSTANT(name));
+  return Result::SUCCESS;
+}
+
+Result perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
+                                    const char* trackName, const char* name) {
+  PERFETTO_TE(
+      category, PERFETTO_TE_INSTANT(name),
+      PERFETTO_TE_NAMED_TRACK(trackName, 1, PerfettoTeProcessTrackUuid()));
+  return Result::SUCCESS;
+}
+
+Result perfettoTraceCounter(const struct PerfettoTeCategory& category,
+                            [[maybe_unused]] const char* name, int64_t value) {
+  PERFETTO_TE(category, PERFETTO_TE_COUNTER(),
+              PERFETTO_TE_INT_COUNTER(value));
+  return Result::SUCCESS;
+}
+
+uint64_t getDefaultCategories() {
+  return TRACE_CATEGORIES;
+}
+
+}  // namespace internal
+
+}  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h
new file mode 100644
index 0000000..79e4b8f
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 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 TRACING_PERFETTO_INTERNAL_H
+#define TRACING_PERFETTO_INTERNAL_H
+
+#include <stdint.h>
+
+#include "include/trace_result.h"
+#include "perfetto/public/te_category_macros.h"
+
+namespace tracing_perfetto {
+
+namespace internal {
+
+bool isPerfettoRegistered();
+
+struct PerfettoTeCategory* toPerfettoCategory(uint64_t category);
+
+void registerWithPerfetto(bool test = false);
+
+Result perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name);
+
+Result perfettoTraceEnd(const struct PerfettoTeCategory& category);
+
+Result perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
+                               uint64_t cookie);
+
+Result perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
+                             uint64_t cookie);
+
+Result perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
+                                       const char* trackName, uint64_t cookie);
+
+Result perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
+                                     const char* trackName, uint64_t cookie);
+
+Result perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name);
+
+Result perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
+                                    const char* trackName, const char* name);
+
+Result perfettoTraceCounter(const struct PerfettoTeCategory& category, const char* name,
+                            int64_t value);
+
+uint64_t getDefaultCategories();
+
+}  // namespace internal
+
+}  // namespace tracing_perfetto
+
+#endif  // TRACING_PERFETTO_INTERNAL_H
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index f84d145..12230f9 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -14,6 +14,7 @@
 
 package {
     default_applicable_licenses: ["frameworks_native_libs_ui_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 // Added automatically by a large-scale-change
@@ -276,13 +277,3 @@
     "tests",
     "tools",
 ]
-
-filegroup {
-    name: "libui_host_common",
-    srcs: [
-        "Rect.cpp",
-        "Region.cpp",
-        "PixelFormat.cpp",
-        "Transform.cpp",
-    ],
-}
diff --git a/libs/ui/DebugUtils.cpp b/libs/ui/DebugUtils.cpp
index 8675f14..bee58e5 100644
--- a/libs/ui/DebugUtils.cpp
+++ b/libs/ui/DebugUtils.cpp
@@ -22,14 +22,12 @@
 #include <android-base/stringprintf.h>
 #include <string>
 
-using android::base::StringAppendF;
 using android::base::StringPrintf;
 using android::ui::ColorMode;
 using android::ui::RenderIntent;
 
-std::string decodeStandard(android_dataspace dataspace) {
-    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
-    switch (dataspaceSelect) {
+std::string decodeStandardOnly(uint32_t dataspaceStandard) {
+    switch (dataspaceStandard) {
         case HAL_DATASPACE_STANDARD_BT709:
             return std::string("BT709");
 
@@ -62,63 +60,44 @@
 
         case HAL_DATASPACE_STANDARD_ADOBE_RGB:
             return std::string("AdobeRGB");
-
-        case 0:
-            switch (dataspace & 0xffff) {
-                case HAL_DATASPACE_JFIF:
-                    return std::string("(deprecated) JFIF (BT601_625)");
-
-                case HAL_DATASPACE_BT601_625:
-                    return std::string("(deprecated) BT601_625");
-
-                case HAL_DATASPACE_BT601_525:
-                    return std::string("(deprecated) BT601_525");
-
-                case HAL_DATASPACE_SRGB_LINEAR:
-                case HAL_DATASPACE_SRGB:
-                    return std::string("(deprecated) sRGB");
-
-                case HAL_DATASPACE_BT709:
-                    return std::string("(deprecated) BT709");
-
-                case HAL_DATASPACE_ARBITRARY:
-                    return std::string("ARBITRARY");
-
-                case HAL_DATASPACE_UNKNOWN:
-                // Fallthrough
-                default:
-                    return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
-            }
     }
 
-    return StringPrintf("Unknown dataspace code %d", dataspaceSelect);
+    return StringPrintf("Unknown dataspace code %d", dataspaceStandard);
 }
 
-std::string decodeTransfer(android_dataspace dataspace) {
-    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
-    if (dataspaceSelect == 0) {
+std::string decodeStandard(android_dataspace dataspace) {
+    const uint32_t dataspaceStandard = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+    if (dataspaceStandard == 0) {
         switch (dataspace & 0xffff) {
             case HAL_DATASPACE_JFIF:
+                return std::string("(deprecated) JFIF (BT601_625)");
+
             case HAL_DATASPACE_BT601_625:
+                return std::string("(deprecated) BT601_625");
+
             case HAL_DATASPACE_BT601_525:
-            case HAL_DATASPACE_BT709:
-                return std::string("SMPTE_170M");
+                return std::string("(deprecated) BT601_525");
 
             case HAL_DATASPACE_SRGB_LINEAR:
-            case HAL_DATASPACE_ARBITRARY:
-                return std::string("Linear");
-
             case HAL_DATASPACE_SRGB:
-                return std::string("sRGB");
+                return std::string("(deprecated) sRGB");
+
+            case HAL_DATASPACE_BT709:
+                return std::string("(deprecated) BT709");
+
+            case HAL_DATASPACE_ARBITRARY:
+                return std::string("ARBITRARY");
 
             case HAL_DATASPACE_UNKNOWN:
             // Fallthrough
             default:
-                return std::string("");
+                return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
         }
     }
+    return decodeStandardOnly(dataspaceStandard);
+}
 
-    const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer) {
     switch (dataspaceTransfer) {
         case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
             return std::string("Unspecified");
@@ -151,6 +130,52 @@
     return StringPrintf("Unknown dataspace transfer %d", dataspaceTransfer);
 }
 
+std::string decodeTransfer(android_dataspace dataspace) {
+    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+    if (dataspaceSelect == 0) {
+        switch (dataspace & 0xffff) {
+            case HAL_DATASPACE_JFIF:
+            case HAL_DATASPACE_BT601_625:
+            case HAL_DATASPACE_BT601_525:
+            case HAL_DATASPACE_BT709:
+                return std::string("SMPTE_170M");
+
+            case HAL_DATASPACE_SRGB_LINEAR:
+            case HAL_DATASPACE_ARBITRARY:
+                return std::string("Linear");
+
+            case HAL_DATASPACE_SRGB:
+                return std::string("sRGB");
+
+            case HAL_DATASPACE_UNKNOWN:
+            // Fallthrough
+            default:
+                return std::string("");
+        }
+    }
+
+    const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+    return decodeTransferOnly(dataspaceTransfer);
+}
+
+std::string decodeRangeOnly(uint32_t dataspaceRange) {
+    switch (dataspaceRange) {
+        case HAL_DATASPACE_RANGE_UNSPECIFIED:
+            return std::string("Range Unspecified");
+
+        case HAL_DATASPACE_RANGE_FULL:
+            return std::string("Full range");
+
+        case HAL_DATASPACE_RANGE_LIMITED:
+            return std::string("Limited range");
+
+        case HAL_DATASPACE_RANGE_EXTENDED:
+            return std::string("Extended range");
+    }
+
+    return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+}
+
 std::string decodeRange(android_dataspace dataspace) {
     const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
     if (dataspaceSelect == 0) {
@@ -174,21 +199,7 @@
     }
 
     const uint32_t dataspaceRange = (dataspace & HAL_DATASPACE_RANGE_MASK);
-    switch (dataspaceRange) {
-        case HAL_DATASPACE_RANGE_UNSPECIFIED:
-            return std::string("Range Unspecified");
-
-        case HAL_DATASPACE_RANGE_FULL:
-            return std::string("Full range");
-
-        case HAL_DATASPACE_RANGE_LIMITED:
-            return std::string("Limited range");
-
-        case HAL_DATASPACE_RANGE_EXTENDED:
-            return std::string("Extended range");
-    }
-
-    return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+    return decodeRangeOnly(dataspaceRange);
 }
 
 std::string dataspaceDetails(android_dataspace dataspace) {
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index 82e5427..0908ae8 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -23,67 +23,13 @@
 #include <optional>
 #include <span>
 
+#include <ftl/hash.h>
 #include <log/log.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);
-}
-
-__attribute__((no_sanitize("unsigned-integer-overflow")))
-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;
-}
-
-__attribute__((no_sanitize("unsigned-integer-overflow")))
-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::span<const uint8_t>;
 
 constexpr size_t kEdidBlockSize = 128;
@@ -320,7 +266,7 @@
     // Hash model string instead of using product code or (integer) serial number, since the latter
     // have been observed to change on some displays with multiple inputs. Use a stable hash instead
     // of std::hash which is only required to be same within a single execution of a program.
-    const uint32_t modelHash = static_cast<uint32_t>(cityHash64Len0To16(modelString));
+    const uint32_t modelHash = static_cast<uint32_t>(*ftl::stable_hash(modelString));
 
     // Parse extension blocks.
     std::optional<Cea861ExtensionBlock> cea861Block;
@@ -394,13 +340,4 @@
     return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
 }
 
-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
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index e9b5dec..a5aca99 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -384,8 +384,8 @@
 
 status_t Gralloc2Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height,
                                      PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                     uint32_t bufferCount, uint32_t* outStride,
-                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
+                                     uint32_t* outStride, buffer_handle_t* outBufferHandles,
+                                     bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo = {};
     descriptorInfo.width = width;
     descriptorInfo.height = height;
@@ -400,6 +400,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     auto ret = mAllocator->allocate(descriptor, bufferCount,
                                     [&](const auto& tmpError, const auto& tmpStride,
                                         const auto& tmpBuffers) {
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index 474d381..152b35a 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -371,7 +371,7 @@
 
 status_t Gralloc3Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                                     uint64_t usage, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
     sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo);
@@ -383,6 +383,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     auto ret = mAllocator->allocate(descriptor, bufferCount,
                                     [&](const auto& tmpError, const auto& tmpStride,
                                         const auto& tmpBuffers) {
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 03ff58a..2a60730 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -262,37 +262,8 @@
 status_t Gralloc4Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect& bounds,
                               int acquireFence, void** outData, int32_t* outBytesPerPixel,
                               int32_t* outBytesPerStride) const {
-    std::vector<ui::PlaneLayout> planeLayouts;
-    status_t err = getPlaneLayouts(bufferHandle, &planeLayouts);
-
-    if (err == NO_ERROR && !planeLayouts.empty()) {
-        if (outBytesPerPixel) {
-            int32_t bitsPerPixel = planeLayouts.front().sampleIncrementInBits;
-            for (const auto& planeLayout : planeLayouts) {
-                if (bitsPerPixel != planeLayout.sampleIncrementInBits) {
-                    bitsPerPixel = -1;
-                }
-            }
-            if (bitsPerPixel >= 0 && bitsPerPixel % 8 == 0) {
-                *outBytesPerPixel = bitsPerPixel / 8;
-            } else {
-                *outBytesPerPixel = -1;
-            }
-        }
-        if (outBytesPerStride) {
-            int32_t bytesPerStride = planeLayouts.front().strideInBytes;
-            for (const auto& planeLayout : planeLayouts) {
-                if (bytesPerStride != planeLayout.strideInBytes) {
-                    bytesPerStride = -1;
-                }
-            }
-            if (bytesPerStride >= 0) {
-                *outBytesPerStride = bytesPerStride;
-            } else {
-                *outBytesPerStride = -1;
-            }
-        }
-    }
+    if (outBytesPerPixel) *outBytesPerPixel = -1;
+    if (outBytesPerStride) *outBytesPerStride = -1;
 
     auto buffer = const_cast<native_handle_t*>(bufferHandle);
 
@@ -1069,7 +1040,7 @@
 
 status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                                     uint64_t usage, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
     if (auto error = sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage,
@@ -1084,6 +1055,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     if (mAidlAllocator) {
         AllocationResult result;
 #pragma clang diagnostic push
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index 303043a..f14a5cf 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -19,6 +19,7 @@
 
 #include <ui/Gralloc5.h>
 
+#include <aidl/android/hardware/graphics/allocator/AllocationError.h>
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android/binder_manager.h>
 #include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
@@ -223,55 +224,75 @@
 
 status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t *outStride,
-                                     buffer_handle_t *outBufferHandles, bool importBuffers) const {
-    auto descriptorInfo = makeDescriptor(requestorName, width, height, format, layerCount, usage);
+                                     uint64_t usage, uint32_t* outStride,
+                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
+    auto result = allocate(GraphicBufferAllocator::AllocationRequest{
+            .importBuffer = importBuffers,
+            .width = width,
+            .height = height,
+            .format = format,
+            .layerCount = layerCount,
+            .usage = usage,
+            .requestorName = requestorName,
+    });
+
+    *outStride = result.stride;
+    outBufferHandles[0] = result.handle;
+    return result.status;
+}
+
+GraphicBufferAllocator::AllocationResult Gralloc5Allocator::allocate(
+        const GraphicBufferAllocator::AllocationRequest& request) const {
+    auto descriptorInfo = makeDescriptor(request.requestorName, request.width, request.height,
+                                         request.format, request.layerCount, request.usage);
     if (!descriptorInfo) {
-        return BAD_VALUE;
+        return GraphicBufferAllocator::AllocationResult{BAD_VALUE};
+    }
+
+    descriptorInfo->additionalOptions.reserve(request.extras.size());
+    for (const auto& option : request.extras) {
+        ExtendableType type;
+        type.name = option.name;
+        type.value = option.value;
+        descriptorInfo->additionalOptions.push_back(std::move(type));
     }
 
     AllocationResult result;
-    auto status = mAllocator->allocate2(*descriptorInfo, bufferCount, &result);
+    auto status = mAllocator->allocate2(*descriptorInfo, 1, &result);
     if (!status.isOk()) {
         auto error = status.getExceptionCode();
         if (error == EX_SERVICE_SPECIFIC) {
-            error = status.getServiceSpecificError();
+            switch (static_cast<AllocationError>(status.getServiceSpecificError())) {
+                case AllocationError::BAD_DESCRIPTOR:
+                    error = BAD_VALUE;
+                    break;
+                case AllocationError::NO_RESOURCES:
+                    error = NO_MEMORY;
+                    break;
+                default:
+                    error = UNKNOWN_ERROR;
+                    break;
+            }
         }
-        if (error == OK) {
-            error = UNKNOWN_ERROR;
-        }
-        return error;
+        return GraphicBufferAllocator::AllocationResult{error};
     }
 
-    if (importBuffers) {
-        for (uint32_t i = 0; i < bufferCount; i++) {
-            auto handle = makeFromAidl(result.buffers[i]);
-            auto error = mMapper.importBuffer(handle, &outBufferHandles[i]);
-            native_handle_delete(handle);
-            if (error != NO_ERROR) {
-                for (uint32_t j = 0; j < i; j++) {
-                    mMapper.freeBuffer(outBufferHandles[j]);
-                    outBufferHandles[j] = nullptr;
-                }
-                return error;
-            }
+    GraphicBufferAllocator::AllocationResult ret{OK};
+    if (request.importBuffer) {
+        auto handle = makeFromAidl(result.buffers[0]);
+        auto error = mMapper.importBuffer(handle, &ret.handle);
+        native_handle_delete(handle);
+        if (error != NO_ERROR) {
+            return GraphicBufferAllocator::AllocationResult{error};
         }
     } 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;
-                }
-                return NO_MEMORY;
-            }
+        ret.handle = dupFromAidl(result.buffers[0]);
+        if (!ret.handle) {
+            return GraphicBufferAllocator::AllocationResult{NO_MEMORY};
         }
     }
 
-    *outStride = result.stride;
+    ret.stride = result.stride;
 
     // Release all the resources held by AllocationResult (specifically any remaining FDs)
     result = {};
@@ -280,7 +301,7 @@
     // is marked apex_available (b/214400477) and libbinder isn't (which of course is correct)
     // IPCThreadState::self()->flushCommands();
 
-    return OK;
+    return ret;
 }
 
 void Gralloc5Mapper::preload() {
@@ -576,37 +597,8 @@
 status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
                               int acquireFence, void **outData, int32_t *outBytesPerPixel,
                               int32_t *outBytesPerStride) const {
-    std::vector<ui::PlaneLayout> planeLayouts;
-    status_t err = getPlaneLayouts(bufferHandle, &planeLayouts);
-
-    if (err == NO_ERROR && !planeLayouts.empty()) {
-        if (outBytesPerPixel) {
-            int32_t bitsPerPixel = planeLayouts.front().sampleIncrementInBits;
-            for (const auto &planeLayout : planeLayouts) {
-                if (bitsPerPixel != planeLayout.sampleIncrementInBits) {
-                    bitsPerPixel = -1;
-                }
-            }
-            if (bitsPerPixel >= 0 && bitsPerPixel % 8 == 0) {
-                *outBytesPerPixel = bitsPerPixel / 8;
-            } else {
-                *outBytesPerPixel = -1;
-            }
-        }
-        if (outBytesPerStride) {
-            int32_t bytesPerStride = planeLayouts.front().strideInBytes;
-            for (const auto &planeLayout : planeLayouts) {
-                if (bytesPerStride != planeLayout.strideInBytes) {
-                    bytesPerStride = -1;
-                }
-            }
-            if (bytesPerStride >= 0) {
-                *outBytesPerStride = bytesPerStride;
-            } else {
-                *outBytesPerStride = -1;
-            }
-        }
-    }
+    if (outBytesPerPixel) *outBytesPerPixel = -1;
+    if (outBytesPerStride) *outBytesPerStride = -1;
 
     auto status = mMapper->v5.lock(bufferHandle, usage, bounds, acquireFence, outData);
 
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index 429760f..ffb6cdb 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -22,7 +22,7 @@
 #include <cutils/atomic.h>
 
 #include <grallocusage/GrallocUsageConversion.h>
-
+#include <sync/sync.h>
 #include <ui/GraphicBufferAllocator.h>
 #include <ui/GraphicBufferMapper.h>
 #include <utils/Trace.h>
@@ -40,6 +40,38 @@
     return id;
 }
 
+static void resolveLegacyByteLayoutFromPlaneLayout(const std::vector<ui::PlaneLayout>& planeLayouts,
+                                                   int32_t* outBytesPerPixel,
+                                                   int32_t* outBytesPerStride) {
+    if (planeLayouts.empty()) return;
+    if (outBytesPerPixel) {
+        int32_t bitsPerPixel = planeLayouts.front().sampleIncrementInBits;
+        for (const auto& planeLayout : planeLayouts) {
+            if (bitsPerPixel != planeLayout.sampleIncrementInBits) {
+                bitsPerPixel = -1;
+            }
+        }
+        if (bitsPerPixel >= 0 && bitsPerPixel % 8 == 0) {
+            *outBytesPerPixel = bitsPerPixel / 8;
+        } else {
+            *outBytesPerPixel = -1;
+        }
+    }
+    if (outBytesPerStride) {
+        int32_t bytesPerStride = planeLayouts.front().strideInBytes;
+        for (const auto& planeLayout : planeLayouts) {
+            if (bytesPerStride != planeLayout.strideInBytes) {
+                bytesPerStride = -1;
+            }
+        }
+        if (bytesPerStride >= 0) {
+            *outBytesPerStride = bytesPerStride;
+        } else {
+            *outBytesPerStride = -1;
+        }
+    }
+}
+
 sp<GraphicBuffer> GraphicBuffer::from(ANativeWindowBuffer* anwb) {
     return static_cast<GraphicBuffer *>(anwb);
 }
@@ -106,6 +138,26 @@
                                 inUsage, inStride);
 }
 
+GraphicBuffer::GraphicBuffer(const GraphicBufferAllocator::AllocationRequest& request)
+      : GraphicBuffer() {
+    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
+    auto result = allocator.allocate(request);
+    mInitCheck = result.status;
+    if (result.status == NO_ERROR) {
+        handle = result.handle;
+        stride = result.stride;
+
+        mBufferMapper.getTransportSize(handle, &mTransportNumFds, &mTransportNumInts);
+
+        width = static_cast<int>(request.width);
+        height = static_cast<int>(request.height);
+        format = request.format;
+        layerCount = request.layerCount;
+        usage = request.usage;
+        usage_deprecated = int(usage);
+    }
+}
+
 GraphicBuffer::~GraphicBuffer()
 {
     ATRACE_CALL();
@@ -143,6 +195,10 @@
             const_cast<GraphicBuffer*>(this));
 }
 
+status_t GraphicBuffer::getDataspace(ui::Dataspace* outDataspace) const {
+    return mBufferMapper.getDataspace(handle, outDataspace);
+}
+
 status_t GraphicBuffer::reallocate(uint32_t inWidth, uint32_t inHeight,
         PixelFormat inFormat, uint32_t inLayerCount, uint64_t inUsage)
 {
@@ -255,10 +311,7 @@
         return BAD_VALUE;
     }
 
-    status_t res = getBufferMapper().lock(handle, inUsage, rect, vaddr, outBytesPerPixel,
-                                          outBytesPerStride);
-
-    return res;
+    return lockAsync(inUsage, rect, vaddr, -1, outBytesPerPixel, outBytesPerStride);
 }
 
 status_t GraphicBuffer::lockYCbCr(uint32_t inUsage, android_ycbcr* ycbcr)
@@ -278,14 +331,12 @@
                 width, height);
         return BAD_VALUE;
     }
-    status_t res = getBufferMapper().lockYCbCr(handle, inUsage, rect, ycbcr);
-    return res;
+    return lockAsyncYCbCr(inUsage, rect, ycbcr, -1);
 }
 
 status_t GraphicBuffer::unlock()
 {
-    status_t res = getBufferMapper().unlock(handle);
-    return res;
+    return unlockAsync(nullptr);
 }
 
 status_t GraphicBuffer::lockAsync(uint32_t inUsage, void** vaddr, int fenceFd,
@@ -312,10 +363,49 @@
         return BAD_VALUE;
     }
 
-    status_t res = getBufferMapper().lockAsync(handle, inProducerUsage, inConsumerUsage, rect,
-                                               vaddr, fenceFd, outBytesPerPixel, outBytesPerStride);
+    // Resolve the bpp & bps before doing a lock in case this fails we don't have to worry about
+    // doing an unlock
+    int32_t legacyBpp = -1, legacyBps = -1;
+    if (outBytesPerPixel || outBytesPerStride) {
+        const auto mapperVersion = getBufferMapperVersion();
+        // For gralloc2 we need to guess at the bpp & bps
+        // For gralloc3 the lock() call will return it
+        // For gralloc4 & later the PlaneLayout metadata query is vastly superior and we
+        // resolve bpp & bps just for compatibility
 
-    return res;
+        // TODO: See if we can't just remove gralloc2 support.
+        if (mapperVersion == GraphicBufferMapper::GRALLOC_2) {
+            legacyBpp = bytesPerPixel(format);
+            if (legacyBpp > 0) {
+                legacyBps = stride * legacyBpp;
+            } else {
+                legacyBpp = -1;
+            }
+        } else if (mapperVersion >= GraphicBufferMapper::GRALLOC_4) {
+            auto planeLayout = getBufferMapper().getPlaneLayouts(handle);
+            if (!planeLayout.has_value()) return planeLayout.asStatus();
+            resolveLegacyByteLayoutFromPlaneLayout(planeLayout.value(), &legacyBpp, &legacyBps);
+        }
+    }
+
+    const uint64_t usage = static_cast<uint64_t>(
+            android_convertGralloc1To0Usage(inProducerUsage, inConsumerUsage));
+
+    auto result = getBufferMapper().lock(handle, usage, rect, base::unique_fd{fenceFd});
+
+    if (!result.has_value()) {
+        return result.error().asStatus();
+    }
+    auto value = result.value();
+    *vaddr = value.address;
+
+    if (outBytesPerPixel) {
+        *outBytesPerPixel = legacyBpp != -1 ? legacyBpp : value.bytesPerPixel;
+    }
+    if (outBytesPerStride) {
+        *outBytesPerStride = legacyBps != -1 ? legacyBps : value.bytesPerStride;
+    }
+    return OK;
 }
 
 status_t GraphicBuffer::lockAsyncYCbCr(uint32_t inUsage, android_ycbcr* ycbcr,
@@ -336,14 +426,18 @@
                 width, height);
         return BAD_VALUE;
     }
-    status_t res = getBufferMapper().lockAsyncYCbCr(handle, inUsage, rect, ycbcr, fenceFd);
-    return res;
+    auto result = getBufferMapper().lockYCbCr(handle, static_cast<int64_t>(inUsage), rect,
+                                              base::unique_fd{fenceFd});
+    if (!result.has_value()) {
+        return result.error().asStatus();
+    }
+    *ycbcr = result.value();
+    return OK;
 }
 
 status_t GraphicBuffer::unlockAsync(int *fenceFd)
 {
-    status_t res = getBufferMapper().unlockAsync(handle, fenceFd);
-    return res;
+    return getBufferMapper().unlockAsync(handle, fenceFd);
 }
 
 status_t GraphicBuffer::isSupported(uint32_t inWidth, uint32_t inHeight, PixelFormat inFormat,
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index eb0bd4e..1ebe597 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -113,6 +113,79 @@
     ALOGD("%s", s.c_str());
 }
 
+auto GraphicBufferAllocator::allocate(const AllocationRequest& request) -> AllocationResult {
+    ATRACE_CALL();
+    if (!request.width || !request.height) {
+        return AllocationResult(BAD_VALUE);
+    }
+
+    const auto width = request.width;
+    const auto height = request.height;
+
+    const uint32_t bpp = bytesPerPixel(request.format);
+    if (std::numeric_limits<size_t>::max() / width / height < static_cast<size_t>(bpp)) {
+        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
+              "usage %" PRIx64 ": Requesting too large a buffer size",
+              request.width, request.height, request.layerCount, request.format, request.usage);
+        return AllocationResult(BAD_VALUE);
+    }
+
+    if (request.layerCount < 1) {
+        return AllocationResult(BAD_VALUE);
+    }
+
+    auto result = mAllocator->allocate(request);
+    if (result.status == UNKNOWN_TRANSACTION) {
+        if (!request.extras.empty()) {
+            ALOGE("Failed to allocate with additional options, allocator version mis-match? "
+                  "gralloc version = %d",
+                  (int)mMapper.getMapperVersion());
+            return result;
+        }
+        // If there's no additional options, fall back to previous allocate version
+        result.status = mAllocator->allocate(request.requestorName, request.width, request.height,
+                                             request.format, request.layerCount, request.usage,
+                                             &result.stride, &result.handle, request.importBuffer);
+    }
+
+    if (result.status != NO_ERROR) {
+        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
+              "usage %" PRIx64 ": %d",
+              request.width, request.height, request.layerCount, request.format, request.usage,
+              result.status);
+        return result;
+    }
+
+    if (!request.importBuffer) {
+        return result;
+    }
+    size_t bufSize;
+
+    // if stride has no meaning or is too large,
+    // approximate size with the input width instead
+    if ((result.stride) != 0 &&
+        std::numeric_limits<size_t>::max() / height / (result.stride) < static_cast<size_t>(bpp)) {
+        bufSize = static_cast<size_t>(width) * height * bpp;
+    } else {
+        bufSize = static_cast<size_t>((result.stride)) * height * bpp;
+    }
+
+    Mutex::Autolock _l(sLock);
+    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
+    alloc_rec_t rec;
+    rec.width = width;
+    rec.height = height;
+    rec.stride = result.stride;
+    rec.format = request.format;
+    rec.layerCount = request.layerCount;
+    rec.usage = request.usage;
+    rec.size = bufSize;
+    rec.requestorName = request.requestorName;
+    list.add(result.handle, rec);
+
+    return result;
+}
+
 status_t GraphicBufferAllocator::allocateHelper(uint32_t width, uint32_t height, PixelFormat format,
                                                 uint32_t layerCount, uint64_t usage,
                                                 buffer_handle_t* handle, uint32_t* stride,
@@ -141,7 +214,7 @@
     usage &= ~static_cast<uint64_t>((1 << 10) | (1 << 13));
 
     status_t error = mAllocator->allocate(requestorName, width, height, format, layerCount, usage,
-                                          1, stride, handle, importBuffer);
+                                          stride, handle, importBuffer);
     if (error != NO_ERROR) {
         ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
               "usage %" PRIx64 ": %d",
@@ -218,5 +291,9 @@
     return NO_ERROR;
 }
 
+bool GraphicBufferAllocator::supportsAdditionalOptions() const {
+    return mAllocator->supportsAdditionalOptions();
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index dc026d2..b6ab2f5 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -41,9 +41,13 @@
 
 #include <system/graphics.h>
 
+using unique_fd = ::android::base::unique_fd;
+
 namespace android {
 // ---------------------------------------------------------------------------
 
+using LockResult = GraphicBufferMapper::LockResult;
+
 ANDROID_SINGLETON_STATIC_INSTANCE( GraphicBufferMapper )
 
 void GraphicBufferMapper::preloadHal() {
@@ -135,63 +139,86 @@
     return NO_ERROR;
 }
 
-status_t GraphicBufferMapper::lock(buffer_handle_t handle, uint32_t usage, const Rect& bounds,
-                                   void** vaddr, int32_t* outBytesPerPixel,
-                                   int32_t* outBytesPerStride) {
-    return lockAsync(handle, usage, bounds, vaddr, -1, outBytesPerPixel, outBytesPerStride);
-}
+ui::Result<LockResult> GraphicBufferMapper::lock(buffer_handle_t handle, int64_t usage,
+                                                 const Rect& bounds, unique_fd&& acquireFence) {
+    ATRACE_CALL();
 
-status_t GraphicBufferMapper::lockYCbCr(buffer_handle_t handle, uint32_t usage,
-        const Rect& bounds, android_ycbcr *ycbcr)
-{
-    return lockAsyncYCbCr(handle, usage, bounds, ycbcr, -1);
-}
-
-status_t GraphicBufferMapper::unlock(buffer_handle_t handle)
-{
-    int32_t fenceFd = -1;
-    status_t error = unlockAsync(handle, &fenceFd);
-    if (error == NO_ERROR && fenceFd >= 0) {
-        sync_wait(fenceFd, -1);
-        close(fenceFd);
+    LockResult result;
+    status_t status = mMapper->lock(handle, usage, bounds, acquireFence.release(), &result.address,
+                                    &result.bytesPerPixel, &result.bytesPerStride);
+    if (status != OK) {
+        return base::unexpected(ui::Error::statusToCode(status));
+    } else {
+        return result;
     }
-    return error;
+}
+
+ui::Result<android_ycbcr> GraphicBufferMapper::lockYCbCr(buffer_handle_t handle, int64_t usage,
+                                                         const Rect& bounds,
+                                                         base::unique_fd&& acquireFence) {
+    ATRACE_CALL();
+
+    android_ycbcr result = {};
+    status_t status = mMapper->lock(handle, usage, bounds, acquireFence.release(), &result);
+    if (status != OK) {
+        return base::unexpected(ui::Error::statusToCode(status));
+    } else {
+        return result;
+    }
+}
+
+status_t GraphicBufferMapper::unlock(buffer_handle_t handle, base::unique_fd* outFence) {
+    ATRACE_CALL();
+    int fence = mMapper->unlock(handle);
+    if (outFence) {
+        *outFence = unique_fd{fence};
+    } else {
+        sync_wait(fence, -1);
+        close(fence);
+    }
+    return OK;
+}
+
+status_t GraphicBufferMapper::lock(buffer_handle_t handle, uint32_t usage, const Rect& bounds,
+                                   void** vaddr) {
+    auto result = lock(handle, static_cast<int64_t>(usage), bounds);
+    if (!result.has_value()) return result.asStatus();
+    auto val = result.value();
+    *vaddr = val.address;
+    return OK;
+}
+
+status_t GraphicBufferMapper::lockYCbCr(buffer_handle_t handle, uint32_t usage, const Rect& bounds,
+                                        android_ycbcr* ycbcr) {
+    auto result = lockYCbCr(handle, static_cast<int64_t>(usage), bounds);
+    if (!result.has_value()) return result.asStatus();
+    *ycbcr = result.value();
+    return OK;
 }
 
 status_t GraphicBufferMapper::lockAsync(buffer_handle_t handle, uint32_t usage, const Rect& bounds,
-                                        void** vaddr, int fenceFd, int32_t* outBytesPerPixel,
-                                        int32_t* outBytesPerStride) {
-    return lockAsync(handle, usage, usage, bounds, vaddr, fenceFd, outBytesPerPixel,
-                     outBytesPerStride);
+                                        void** vaddr, int fenceFd) {
+    auto result = lock(handle, static_cast<int64_t>(usage), bounds, unique_fd{fenceFd});
+    if (!result.has_value()) return result.asStatus();
+    auto val = result.value();
+    *vaddr = val.address;
+    return OK;
 }
 
 status_t GraphicBufferMapper::lockAsync(buffer_handle_t handle, uint64_t producerUsage,
                                         uint64_t consumerUsage, const Rect& bounds, void** vaddr,
-                                        int fenceFd, int32_t* outBytesPerPixel,
-                                        int32_t* outBytesPerStride) {
-    ATRACE_CALL();
-
-    const uint64_t usage = static_cast<uint64_t>(ANDROID_NATIVE_UNSIGNED_CAST(
-            android_convertGralloc1To0Usage(producerUsage, consumerUsage)));
-    return mMapper->lock(handle, usage, bounds, fenceFd, vaddr, outBytesPerPixel,
-                         outBytesPerStride);
+                                        int fenceFd) {
+    return lockAsync(handle, android_convertGralloc1To0Usage(producerUsage, consumerUsage), bounds,
+                     vaddr, fenceFd);
 }
 
-status_t GraphicBufferMapper::lockAsyncYCbCr(buffer_handle_t handle,
-        uint32_t usage, const Rect& bounds, android_ycbcr *ycbcr, int fenceFd)
-{
-    ATRACE_CALL();
-
-    return mMapper->lock(handle, usage, bounds, fenceFd, ycbcr);
-}
-
-status_t GraphicBufferMapper::unlockAsync(buffer_handle_t handle, int *fenceFd)
-{
-    ATRACE_CALL();
-
-    *fenceFd = mMapper->unlock(handle);
-
-    return NO_ERROR;
+status_t GraphicBufferMapper::lockAsyncYCbCr(buffer_handle_t handle, uint32_t usage,
+                                             const Rect& bounds, android_ycbcr* ycbcr,
+                                             int fenceFd) {
+    auto result = lockYCbCr(handle, static_cast<int64_t>(usage), bounds, unique_fd{fenceFd});
+    if (!result.has_value()) return result.asStatus();
+    *ycbcr = result.value();
+    return OK;
 }
 
 status_t GraphicBufferMapper::isSupported(uint32_t width, uint32_t height,
@@ -287,6 +314,17 @@
     return mMapper->getPlaneLayouts(bufferHandle, outPlaneLayouts);
 }
 
+ui::Result<std::vector<ui::PlaneLayout>> GraphicBufferMapper::getPlaneLayouts(
+        buffer_handle_t bufferHandle) {
+    std::vector<ui::PlaneLayout> temp;
+    status_t status = mMapper->getPlaneLayouts(bufferHandle, &temp);
+    if (status == OK) {
+        return std::move(temp);
+    } else {
+        return base::unexpected(ui::Error::statusToCode(status));
+    }
+}
+
 status_t GraphicBufferMapper::getDataspace(buffer_handle_t bufferHandle,
                                            ui::Dataspace* outDataspace) {
     return mMapper->getDataspace(bufferHandle, outDataspace);
diff --git a/libs/ui/include/ui/DebugUtils.h b/libs/ui/include/ui/DebugUtils.h
index 18cd487..7c4ac42 100644
--- a/libs/ui/include/ui/DebugUtils.h
+++ b/libs/ui/include/ui/DebugUtils.h
@@ -27,8 +27,11 @@
 }
 
 std::string decodeStandard(android_dataspace dataspace);
+std::string decodeStandardOnly(uint32_t dataspaceStandard);
 std::string decodeTransfer(android_dataspace dataspace);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer);
 std::string decodeRange(android_dataspace dataspace);
+std::string decodeRangeOnly(uint32_t dataspaceRange);
 std::string dataspaceDetails(android_dataspace dataspace);
 std::string decodeColorMode(android::ui::ColorMode colormode);
 std::string decodeColorTransform(android_color_transform colorTransform);
diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index fc9c0f4..8bc2017 100644
--- a/libs/ui/include/ui/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -80,7 +80,4 @@
 
 PhysicalDisplayId getVirtualDisplayId(uint32_t id);
 
-// 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/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index 496ba57..4167dcb 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -23,6 +23,7 @@
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
 #include <utils/StrongPointer.h>
+#include "GraphicBufferAllocator.h"
 
 #include <string>
 
@@ -218,9 +219,15 @@
      */
     virtual status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
                               PixelFormat format, uint32_t layerCount, uint64_t usage,
-                              uint32_t bufferCount, uint32_t* outStride,
-                              buffer_handle_t* outBufferHandles,
+                              uint32_t* outStride, buffer_handle_t* outBufferHandles,
                               bool importBuffers = true) const = 0;
+
+    virtual GraphicBufferAllocator::AllocationResult allocate(
+            const GraphicBufferAllocator::AllocationRequest&) const {
+        return GraphicBufferAllocator::AllocationResult(UNKNOWN_TRANSACTION);
+    }
+
+    virtual bool supportsAdditionalOptions() const { return false; }
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h
index a7b6f492..e50bb3a 100644
--- a/libs/ui/include/ui/Gralloc2.h
+++ b/libs/ui/include/ui/Gralloc2.h
@@ -81,9 +81,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc2Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h
index 7367549..035684a 100644
--- a/libs/ui/include/ui/Gralloc3.h
+++ b/libs/ui/include/ui/Gralloc3.h
@@ -82,9 +82,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc3Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index df43be8..0f469c0 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -174,9 +174,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc4Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
index 44b97d1..5aa5019 100644
--- a/libs/ui/include/ui/Gralloc5.h
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -172,10 +172,14 @@
 
     [[nodiscard]] status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
                                     PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                    uint32_t bufferCount, uint32_t *outStride,
-                                    buffer_handle_t *outBufferHandles,
+                                    uint32_t* outStride, buffer_handle_t* outBufferHandles,
                                     bool importBuffers) const override;
 
+    [[nodiscard]] GraphicBufferAllocator::AllocationResult allocate(
+            const GraphicBufferAllocator::AllocationRequest&) const override;
+
+    bool supportsAdditionalOptions() const override { return true; }
+
 private:
     const Gralloc5Mapper &mMapper;
     std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h
index f859848..652d8ba 100644
--- a/libs/ui/include/ui/GraphicBuffer.h
+++ b/libs/ui/include/ui/GraphicBuffer.h
@@ -26,6 +26,7 @@
 
 #include <android/hardware_buffer.h>
 #include <ui/ANativeObjectBase.h>
+#include <ui/GraphicBufferAllocator.h>
 #include <ui/GraphicBufferMapper.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
@@ -103,6 +104,8 @@
             uint32_t inLayerCount, uint64_t inUsage,
             std::string requestorName = "<Unknown>");
 
+    GraphicBuffer(const GraphicBufferAllocator::AllocationRequest&);
+
     // Create a GraphicBuffer from an existing handle.
     enum HandleWrapMethod : uint8_t {
         // Wrap and use the handle directly.  It assumes the handle has been
@@ -169,6 +172,8 @@
         mGenerationNumber = generation;
     }
 
+    status_t getDataspace(ui::Dataspace* outDataspace) const;
+
     // This function is privileged.  It requires access to the allocator
     // device or service, which usually involves adding suitable selinux
     // rules.
diff --git a/libs/ui/include/ui/GraphicBufferAllocator.h b/libs/ui/include/ui/GraphicBufferAllocator.h
index 3ed988c..bbb2d77 100644
--- a/libs/ui/include/ui/GraphicBufferAllocator.h
+++ b/libs/ui/include/ui/GraphicBufferAllocator.h
@@ -22,6 +22,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include <cutils/native_handle.h>
 
@@ -42,6 +43,35 @@
 public:
     static inline GraphicBufferAllocator& get() { return getInstance(); }
 
+    struct AdditionalOptions {
+        const char* name;
+        int64_t value;
+    };
+
+    struct AllocationRequest {
+        bool importBuffer;
+        uint32_t width;
+        uint32_t height;
+        PixelFormat format;
+        uint32_t layerCount;
+        uint64_t usage;
+        std::string requestorName;
+        std::vector<AdditionalOptions> extras;
+    };
+
+    struct AllocationResult {
+        status_t status;
+        buffer_handle_t handle = nullptr;
+        uint32_t stride = 0;
+
+        explicit AllocationResult(status_t status) : status(status) {}
+
+        explicit AllocationResult(buffer_handle_t handle, uint32_t stride)
+              : status(OK), handle(handle), stride(stride) {}
+    };
+
+    AllocationResult allocate(const AllocationRequest&);
+
     /**
      * Allocates and imports a gralloc buffer.
      *
@@ -77,6 +107,8 @@
     void dump(std::string& res, bool less = true) const;
     static void dumpToSystemLog(bool less = true);
 
+    bool supportsAdditionalOptions() const;
+
 protected:
     struct alloc_rec_t {
         uint32_t width;
diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h
index 3a5167a..9da1447 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -22,9 +22,11 @@
 
 #include <memory>
 
+#include <android-base/unique_fd.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
+#include <ui/Result.h>
 #include <utils/Singleton.h>
 
 // Needed by code that still uses the GRALLOC_USAGE_* constants.
@@ -38,6 +40,12 @@
 
 class GrallocMapper;
 
+/**
+ * This class is a thin wrapper over the various gralloc HALs. It is a "raw" wrapper, having
+ * version-specific behaviors & features. It is not recommend for general use. It is instead
+ * strongly recommended to use AHardwareBuffer or ui::GraphicBuffer which will provide stronger
+ * API compatibility & consistency behaviors.
+ */
 class GraphicBufferMapper : public Singleton<GraphicBufferMapper>
 {
 public:
@@ -66,27 +74,50 @@
     void getTransportSize(buffer_handle_t handle,
             uint32_t* outTransportNumFds, uint32_t* outTransportNumInts);
 
-    status_t lock(buffer_handle_t handle, uint32_t usage, const Rect& bounds, void** vaddr,
-                  int32_t* outBytesPerPixel = nullptr, int32_t* outBytesPerStride = nullptr);
+    struct LockResult {
+        void* address = nullptr;
+        /**
+         * Note: bytesPerPixel is only populated if version is gralloc 3
+         * Gralloc 4 & later should use instead getPlaneLayout()
+         */
+        int32_t bytesPerPixel = -1;
+        /**
+         * Note: bytesPerPixel is only populated if version is gralloc 3
+         * Gralloc 4 & later should use instead getPlaneLayout()
+         */
+        int32_t bytesPerStride = -1;
+    };
+
+    ui::Result<LockResult> lock(buffer_handle_t handle, int64_t usage, const Rect& bounds,
+                                base::unique_fd&& acquireFence = {});
+
+    ui::Result<android_ycbcr> lockYCbCr(buffer_handle_t handle, int64_t usage, const Rect& bounds,
+                                        base::unique_fd&& acquireFence = {});
+
+    status_t lock(buffer_handle_t handle, uint32_t usage, const Rect& bounds, void** vaddr);
 
     status_t lockYCbCr(buffer_handle_t handle,
             uint32_t usage, const Rect& bounds, android_ycbcr *ycbcr);
 
-    status_t unlock(buffer_handle_t handle);
-
     status_t lockAsync(buffer_handle_t handle, uint32_t usage, const Rect& bounds, void** vaddr,
-                       int fenceFd, int32_t* outBytesPerPixel = nullptr,
-                       int32_t* outBytesPerStride = nullptr);
+                       int fenceFd);
 
     status_t lockAsync(buffer_handle_t handle, uint64_t producerUsage, uint64_t consumerUsage,
-                       const Rect& bounds, void** vaddr, int fenceFd,
-                       int32_t* outBytesPerPixel = nullptr, int32_t* outBytesPerStride = nullptr);
+                       const Rect& bounds, void** vaddr, int fenceFd);
 
     status_t lockAsyncYCbCr(buffer_handle_t handle,
             uint32_t usage, const Rect& bounds, android_ycbcr *ycbcr,
             int fenceFd);
 
-    status_t unlockAsync(buffer_handle_t handle, int *fenceFd);
+    status_t unlock(buffer_handle_t handle, base::unique_fd* outFence = nullptr);
+    status_t unlockAsync(buffer_handle_t handle, int* fenceFd) {
+        base::unique_fd temp;
+        status_t result = unlock(handle, fenceFd ? &temp : nullptr);
+        if (fenceFd) {
+            *fenceFd = temp.release();
+        }
+        return result;
+    }
 
     status_t isSupported(uint32_t width, uint32_t height, android::PixelFormat format,
                          uint32_t layerCount, uint64_t usage, bool* outSupported);
@@ -122,6 +153,7 @@
     status_t getChromaSiting(buffer_handle_t bufferHandle, ui::ChromaSiting* outChromaSiting);
     status_t getPlaneLayouts(buffer_handle_t bufferHandle,
                              std::vector<ui::PlaneLayout>* outPlaneLayouts);
+    ui::Result<std::vector<ui::PlaneLayout>> getPlaneLayouts(buffer_handle_t bufferHandle);
     status_t getDataspace(buffer_handle_t bufferHandle, ui::Dataspace* outDataspace);
     status_t setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace);
     status_t getBlendMode(buffer_handle_t bufferHandle, ui::BlendMode* outBlendMode);
diff --git a/libs/ui/include/ui/LayerStack.h b/libs/ui/include/ui/LayerStack.h
index d6ffeb7..f4c8ba2 100644
--- a/libs/ui/include/ui/LayerStack.h
+++ b/libs/ui/include/ui/LayerStack.h
@@ -55,6 +55,10 @@
     return lhs.id > rhs.id;
 }
 
+inline bool operator<(LayerStack lhs, LayerStack rhs) {
+    return lhs.id < rhs.id;
+}
+
 // A LayerFilter determines if a layer is included for output to a display.
 struct LayerFilter {
     LayerStack layerStack;
diff --git a/libs/ui/include/ui/LogicalDisplayId.h b/libs/ui/include/ui/LogicalDisplayId.h
new file mode 100644
index 0000000..fd84b12
--- /dev/null
+++ b/libs/ui/include/ui/LogicalDisplayId.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/mixins.h>
+#include <sys/types.h>
+#include <string>
+
+#include <ostream>
+
+namespace android::ui {
+
+// Type-safe wrapper for a logical display id.
+struct LogicalDisplayId : ftl::Constructible<LogicalDisplayId, int32_t>,
+                          ftl::Equatable<LogicalDisplayId>,
+                          ftl::Orderable<LogicalDisplayId> {
+    using Constructible::Constructible;
+
+    constexpr auto val() const { return ftl::to_underlying(*this); }
+
+    constexpr bool isValid() const { return val() >= 0; }
+
+    std::string toString() const { return std::to_string(val()); }
+
+    static const LogicalDisplayId INVALID;
+    static const LogicalDisplayId DEFAULT;
+};
+
+constexpr inline LogicalDisplayId LogicalDisplayId::INVALID{-1};
+constexpr inline LogicalDisplayId LogicalDisplayId::DEFAULT{0};
+
+inline std::ostream& operator<<(std::ostream& stream, LogicalDisplayId displayId) {
+    return stream << displayId.val();
+}
+
+} // namespace android::ui
+
+namespace std {
+template <>
+struct hash<android::ui::LogicalDisplayId> {
+    size_t operator()(const android::ui::LogicalDisplayId& displayId) const {
+        return hash<int32_t>()(displayId.val());
+    }
+};
+} // namespace std
\ No newline at end of file
diff --git a/libs/ui/include/ui/Result.h b/libs/ui/include/ui/Result.h
new file mode 100644
index 0000000..d73c3e2
--- /dev/null
+++ b/libs/ui/include/ui/Result.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/expected.h>
+#include <utils/Errors.h>
+
+namespace android::ui {
+
+enum class ErrorCode : int32_t {
+    /**
+     * No error.
+     */
+    None = 0,
+    /**
+     * Invalid BufferDescriptor.
+     */
+    BadDescriptor = 1,
+    /**
+     * Invalid buffer handle.
+     */
+    BadBuffer = 2,
+    /**
+     * Invalid HardwareBufferDescription.
+     */
+    BadValue = 3,
+    /**
+     * Resource unavailable.
+     */
+    NoResources = 5,
+    /**
+     * Permanent failure.
+     */
+    Unsupported = 7,
+};
+
+class Error {
+public:
+    Error(ErrorCode err) : mCode(err) {}
+
+    Error(ErrorCode err, std::string&& message) : mCode(err), mMessage(std::move(message)) {}
+    Error(ErrorCode err, const std::string_view& message) : mCode(err), mMessage(message) {}
+
+    static constexpr status_t codeToStatus(ErrorCode code) {
+        switch (code) {
+            case ErrorCode::None:
+                return OK;
+            case ErrorCode::BadDescriptor:
+                return BAD_VALUE;
+            case ErrorCode::BadValue:
+                return BAD_VALUE;
+            case ErrorCode::BadBuffer:
+                return BAD_TYPE;
+            case ErrorCode::NoResources:
+                return NO_MEMORY;
+            case ErrorCode::Unsupported:
+                return INVALID_OPERATION;
+            default:
+                return UNKNOWN_ERROR;
+        }
+    }
+
+    static constexpr ErrorCode statusToCode(status_t status) {
+        switch (status) {
+            case OK:
+                return ErrorCode::None;
+            case BAD_VALUE:
+                return ErrorCode::BadValue;
+            case BAD_TYPE:
+                return ErrorCode::BadBuffer;
+            case NO_MEMORY:
+                return ErrorCode::NoResources;
+            case INVALID_OPERATION:
+                return ErrorCode::Unsupported;
+            default:
+                return ErrorCode::Unsupported;
+        }
+    }
+
+    constexpr status_t asStatus() const { return codeToStatus(mCode); }
+
+    ErrorCode code() const { return mCode; }
+
+    const std::string& message() const { return mMessage; }
+
+    bool operator==(const ErrorCode code) { return mCode == code; }
+
+private:
+    ErrorCode mCode;
+    std::string mMessage;
+};
+
+template <typename T>
+class Result : public base::expected<T, Error> {
+public:
+    using base::expected<T, Error>::expected;
+
+    [[nodiscard]] constexpr status_t asStatus() const {
+        return this->has_value() ? OK : this->error().asStatus();
+    }
+
+    [[nodiscard]] constexpr ErrorCode errorCode() const {
+        return this->has_value() ? ErrorCode::None : this->error().code();
+    }
+};
+
+} // namespace android::ui
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index 8ce017d..2d8a1e3 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -21,6 +21,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_libs_ui_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
@@ -54,6 +55,17 @@
 }
 
 cc_test {
+    name: "DisplayIdentification_test",
+    shared_libs: ["libui"],
+    static_libs: ["libgmock"],
+    srcs: ["DisplayIdentification_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
+
+cc_test {
     name: "FlattenableHelpers_test",
     shared_libs: ["libui"],
     srcs: ["FlattenableHelpers_test.cpp"],
diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
index 736979a..721b466 100644
--- a/libs/ui/tests/DisplayIdentification_test.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -21,9 +21,9 @@
 #include <functional>
 #include <string_view>
 
+#include <ftl/hash.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-
 #include <ui/DisplayIdentification.h>
 
 using ::testing::ElementsAre;
@@ -135,7 +135,7 @@
 }
 
 uint32_t hash(const char* str) {
-    return static_cast<uint32_t>(cityHash64Len0To16(str));
+    return static_cast<uint32_t>(*ftl::stable_hash(str));
 }
 
 } // namespace
@@ -188,6 +188,7 @@
     EXPECT_STREQ("SEC", edid->pnpId.data());
     // ASCII text should be used as fallback if display name and serial number are missing.
     EXPECT_EQ(hash("121AT11-801"), edid->modelHash);
+    EXPECT_EQ(hash("121AT11-801"), 626564263);
     EXPECT_TRUE(edid->displayName.empty());
     EXPECT_EQ(12610, edid->productId);
     EXPECT_EQ(21, edid->manufactureOrModelYear);
@@ -199,6 +200,7 @@
     EXPECT_EQ(0x22f0u, edid->manufacturerId);
     EXPECT_STREQ("HWP", edid->pnpId.data());
     EXPECT_EQ(hash("HP ZR30w"), edid->modelHash);
+    EXPECT_EQ(hash("HP ZR30w"), 918492362);
     EXPECT_EQ("HP ZR30w", edid->displayName);
     EXPECT_EQ(10348, edid->productId);
     EXPECT_EQ(22, edid->manufactureOrModelYear);
@@ -210,6 +212,7 @@
     EXPECT_EQ(0x4c2du, edid->manufacturerId);
     EXPECT_STREQ("SAM", edid->pnpId.data());
     EXPECT_EQ(hash("SAMSUNG"), edid->modelHash);
+    EXPECT_EQ(hash("SAMSUNG"), 1201368132);
     EXPECT_EQ("SAMSUNG", edid->displayName);
     EXPECT_EQ(2302, edid->productId);
     EXPECT_EQ(21, edid->manufactureOrModelYear);
@@ -227,6 +230,7 @@
     EXPECT_EQ(13481, edid->manufacturerId);
     EXPECT_STREQ("MEI", edid->pnpId.data());
     EXPECT_EQ(hash("Panasonic-TV"), edid->modelHash);
+    EXPECT_EQ(hash("Panasonic-TV"), 3876373262);
     EXPECT_EQ("Panasonic-TV", edid->displayName);
     EXPECT_EQ(41622, edid->productId);
     EXPECT_EQ(29, edid->manufactureOrModelYear);
@@ -244,6 +248,7 @@
     EXPECT_EQ(8355, edid->manufacturerId);
     EXPECT_STREQ("HEC", edid->pnpId.data());
     EXPECT_EQ(hash("Hisense"), edid->modelHash);
+    EXPECT_EQ(hash("Hisense"), 2859844809);
     EXPECT_EQ("Hisense", edid->displayName);
     EXPECT_EQ(0, edid->productId);
     EXPECT_EQ(29, edid->manufactureOrModelYear);
@@ -261,6 +266,7 @@
     EXPECT_EQ(3724, edid->manufacturerId);
     EXPECT_STREQ("CTL", edid->pnpId.data());
     EXPECT_EQ(hash("LP2361"), edid->modelHash);
+    EXPECT_EQ(hash("LP2361"), 1523181158);
     EXPECT_EQ("LP2361", edid->displayName);
     EXPECT_EQ(9373, edid->productId);
     EXPECT_EQ(23, edid->manufactureOrModelYear);
@@ -281,6 +287,7 @@
     // Serial number should be used as fallback if display name is invalid.
     const auto modelHash = hash("CN4202137Q");
     EXPECT_EQ(modelHash, edid->modelHash);
+    EXPECT_EQ(modelHash, 3582951527);
     EXPECT_TRUE(edid->displayName.empty());
 
     // Parsing should succeed even if EDID is truncated.
diff --git a/libs/ui/tests/GraphicBufferAllocator_test.cpp b/libs/ui/tests/GraphicBufferAllocator_test.cpp
index f4c0afa..efca083 100644
--- a/libs/ui/tests/GraphicBufferAllocator_test.cpp
+++ b/libs/ui/tests/GraphicBufferAllocator_test.cpp
@@ -51,7 +51,7 @@
         std::cout << "Setting expected stride to " << stride << std::endl;
         EXPECT_CALL(*(reinterpret_cast<const mock::MockGrallocAllocator*>(mAllocator.get())),
                     allocate)
-                .WillOnce(DoAll(SetArgPointee<7>(stride), Return(err)));
+                .WillOnce(DoAll(SetArgPointee<6>(stride), Return(err)));
     }
     std::unique_ptr<const GrallocAllocator>& getAllocator() { return mAllocator; }
 };
diff --git a/libs/ui/tests/mock/MockGrallocAllocator.h b/libs/ui/tests/mock/MockGrallocAllocator.h
index d62e3e2..d02b387 100644
--- a/libs/ui/tests/mock/MockGrallocAllocator.h
+++ b/libs/ui/tests/mock/MockGrallocAllocator.h
@@ -35,7 +35,7 @@
     MOCK_METHOD(std::string, dumpDebugInfo, (bool less), (const, override));
     MOCK_METHOD(status_t, allocate,
                 (std::string requestorName, uint32_t width, uint32_t height, PixelFormat format,
-                 uint32_t layerCount, uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                 uint32_t layerCount, uint64_t usage, uint32_t* outStride,
                  buffer_handle_t* outBufferHandles, bool less),
                 (const, override));
 };
diff --git a/libs/ui/tools/Android.bp b/libs/ui/tools/Android.bp
index 5d6070c..cc233b9 100644
--- a/libs/ui/tools/Android.bp
+++ b/libs/ui/tools/Android.bp
@@ -21,6 +21,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_libs_ui_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_defaults {
diff --git a/libs/ultrahdr/OWNERS b/libs/ultrahdr/OWNERS
deleted file mode 100644
index 6ace354..0000000
--- a/libs/ultrahdr/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-arifdikici@google.com
-dichenzhang@google.com
-kyslov@google.com
\ No newline at end of file
diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
deleted file mode 100644
index 2fa361f..0000000
--- a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-license {
-    name: "adobe_hdr_gain_map_license-deprecated",
-    license_kinds: ["legacy_by_exception_only"],
-    license_text: ["NOTICE"],
-}
diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE b/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE
deleted file mode 100644
index 3f6c594..0000000
--- a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE
+++ /dev/null
@@ -1 +0,0 @@
-This product includes Gain Map technology under license by Adobe.
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
deleted file mode 100644
index f1f4035..0000000
--- a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// System include files
-#include <fuzzer/FuzzedDataProvider.h>
-#include <iostream>
-#include <vector>
-
-// User include files
-#include "ultrahdr/jpegr.h"
-
-using namespace android::ultrahdr;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
-const int kOfMax = ULTRAHDR_OUTPUT_MAX;
-
-class UltraHdrDecFuzzer {
-public:
-    UltraHdrDecFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-void UltraHdrDecFuzzer::process() {
-    // hdr_of
-    auto of = static_cast<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
-    auto buffer = mFdp.ConsumeRemainingBytes<uint8_t>();
-    jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(),
-                                     ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-    std::vector<uint8_t> iccData(0);
-    std::vector<uint8_t> exifData(0);
-    jpegr_info_struct info{0, 0, &iccData, &exifData};
-    JpegR jpegHdr;
-    (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info);
-//#define DUMP_PARAM
-#ifdef DUMP_PARAM
-    std::cout << "input buffer size " << jpegImgR.length << std::endl;
-    std::cout << "image dimensions " << info.width << " x " << info.width << std::endl;
-#endif
-    size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
-    jpegr_uncompressed_struct decodedJpegR;
-    auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
-    decodedJpegR.data = decodedRaw.get();
-    ultrahdr_metadata_struct metadata;
-    jpegr_uncompressed_struct decodedGainMap{};
-    (void)jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
-                              mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX), nullptr, of,
-                              &decodedGainMap, &metadata);
-    if (decodedGainMap.data) free(decodedGainMap.data);
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    UltraHdrDecFuzzer fuzzHandle(data, size);
-    fuzzHandle.process();
-    return 0;
-}
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
deleted file mode 100644
index 2d59e8b..0000000
--- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// System include files
-#include <fuzzer/FuzzedDataProvider.h>
-#include <algorithm>
-#include <iostream>
-#include <random>
-#include <vector>
-
-// User include files
-#include "ultrahdr/gainmapmath.h"
-#include "ultrahdr/jpegdecoderhelper.h"
-#include "ultrahdr/jpegencoderhelper.h"
-#include "utils/Log.h"
-
-using namespace android::ultrahdr;
-
-// Color gamuts for image data, sync with ultrahdr.h
-const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1;
-const int kCgMax = ULTRAHDR_COLORGAMUT_MAX;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kTfMin = ULTRAHDR_TF_UNSPECIFIED + 1;
-const int kTfMax = ULTRAHDR_TF_PQ;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
-const int kOfMax = ULTRAHDR_OUTPUT_MAX;
-
-// quality factor
-const int kQfMin = 0;
-const int kQfMax = 100;
-
-class UltraHdrEncFuzzer {
-public:
-    UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-    void fillP010Buffer(uint16_t* data, int width, int height, int stride);
-    void fill420Buffer(uint8_t* data, int width, int height, int stride);
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) {
-    uint16_t* tmp = data;
-    std::vector<uint16_t> buffer(16);
-    for (int i = 0; i < buffer.size(); i++) {
-        buffer[i] = (mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1)) << 6;
-    }
-    for (int j = 0; j < height; j++) {
-        for (int i = 0; i < width; i += buffer.size()) {
-            memcpy(tmp + i, buffer.data(),
-                   std::min((int)buffer.size(), (width - i)) * sizeof(*data));
-            std::shuffle(buffer.begin(), buffer.end(),
-                         std::default_random_engine(std::random_device{}()));
-        }
-        tmp += stride;
-    }
-}
-
-void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int width, int height, int stride) {
-    uint8_t* tmp = data;
-    std::vector<uint8_t> buffer(16);
-    mFdp.ConsumeData(buffer.data(), buffer.size());
-    for (int j = 0; j < height; j++) {
-        for (int i = 0; i < width; i += buffer.size()) {
-            memcpy(tmp + i, buffer.data(),
-                   std::min((int)buffer.size(), (width - i)) * sizeof(*data));
-            std::shuffle(buffer.begin(), buffer.end(),
-                         std::default_random_engine(std::random_device{}()));
-        }
-        tmp += stride;
-    }
-}
-
-void UltraHdrEncFuzzer::process() {
-    while (mFdp.remaining_bytes()) {
-        struct jpegr_uncompressed_struct p010Img {};
-        struct jpegr_uncompressed_struct yuv420Img {};
-        struct jpegr_uncompressed_struct grayImg {};
-        struct jpegr_compressed_struct jpegImgR {};
-        struct jpegr_compressed_struct jpegImg {};
-        struct jpegr_compressed_struct jpegGainMap {};
-
-        // which encode api to select
-        int muxSwitch = mFdp.ConsumeIntegralInRange<int>(0, 4);
-
-        // quality factor
-        int quality = mFdp.ConsumeIntegralInRange<int>(kQfMin, kQfMax);
-
-        // hdr_tf
-        auto tf = static_cast<ultrahdr_transfer_function>(
-                mFdp.ConsumeIntegralInRange<int>(kTfMin, kTfMax));
-
-        // p010 Cg
-        auto p010Cg =
-                static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
-
-        // 420 Cg
-        auto yuv420Cg =
-                static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
-
-        // hdr_of
-        auto of = static_cast<ultrahdr_output_format>(
-                mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
-
-        int width = mFdp.ConsumeIntegralInRange<int>(kMinWidth, kMaxWidth);
-        width = (width >> 1) << 1;
-
-        int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight);
-        height = (height >> 1) << 1;
-
-        std::unique_ptr<uint16_t[]> bufferYHdr = nullptr;
-        std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr;
-        std::unique_ptr<uint8_t[]> bufferYSdr = nullptr;
-        std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr;
-        std::unique_ptr<uint8_t[]> grayImgRaw = nullptr;
-        if (muxSwitch != 4) {
-            // init p010 image
-            bool isUVContiguous = mFdp.ConsumeBool();
-            bool hasYStride = mFdp.ConsumeBool();
-            int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
-            p010Img.width = width;
-            p010Img.height = height;
-            p010Img.colorGamut = p010Cg;
-            p010Img.luma_stride = hasYStride ? yStride : 0;
-            int bppP010 = 2;
-            if (isUVContiguous) {
-                size_t p010Size = yStride * height * 3 / 2;
-                bufferYHdr = std::make_unique<uint16_t[]>(p010Size);
-                p010Img.data = bufferYHdr.get();
-                p010Img.chroma_data = nullptr;
-                p010Img.chroma_stride = 0;
-                fillP010Buffer(bufferYHdr.get(), width, height, yStride);
-                fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride);
-            } else {
-                int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128);
-                size_t p010YSize = yStride * height;
-                bufferYHdr = std::make_unique<uint16_t[]>(p010YSize);
-                p010Img.data = bufferYHdr.get();
-                fillP010Buffer(bufferYHdr.get(), width, height, yStride);
-                size_t p010UVSize = uvStride * p010Img.height / 2;
-                bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize);
-                p010Img.chroma_data = bufferUVHdr.get();
-                p010Img.chroma_stride = uvStride;
-                fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride);
-            }
-        } else {
-            size_t map_width = width / kMapDimensionScaleFactor;
-            size_t map_height = height / kMapDimensionScaleFactor;
-            // init 400 image
-            grayImg.width = map_width;
-            grayImg.height = map_height;
-            grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
-            const size_t graySize = map_width * map_height;
-            grayImgRaw = std::make_unique<uint8_t[]>(graySize);
-            grayImg.data = grayImgRaw.get();
-            fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width);
-            grayImg.chroma_data = nullptr;
-            grayImg.luma_stride = 0;
-            grayImg.chroma_stride = 0;
-        }
-
-        if (muxSwitch > 0) {
-            // init 420 image
-            bool isUVContiguous = mFdp.ConsumeBool();
-            bool hasYStride = mFdp.ConsumeBool();
-            int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
-            yuv420Img.width = width;
-            yuv420Img.height = height;
-            yuv420Img.colorGamut = yuv420Cg;
-            yuv420Img.luma_stride = hasYStride ? yStride : 0;
-            if (isUVContiguous) {
-                size_t yuv420Size = yStride * height * 3 / 2;
-                bufferYSdr = std::make_unique<uint8_t[]>(yuv420Size);
-                yuv420Img.data = bufferYSdr.get();
-                yuv420Img.chroma_data = nullptr;
-                yuv420Img.chroma_stride = 0;
-                fill420Buffer(bufferYSdr.get(), width, height, yStride);
-                fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2,
-                              yStride / 2);
-                fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2,
-                              yStride / 2);
-            } else {
-                int uvStride = mFdp.ConsumeIntegralInRange<int>(width / 2, width / 2 + 128);
-                size_t yuv420YSize = yStride * height;
-                bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize);
-                yuv420Img.data = bufferYSdr.get();
-                fill420Buffer(bufferYSdr.get(), width, height, yStride);
-                size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2;
-                bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize);
-                yuv420Img.chroma_data = bufferUVSdr.get();
-                yuv420Img.chroma_stride = uvStride;
-                fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride);
-                fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2,
-                              uvStride);
-            }
-        }
-
-        // dest
-        // 2 * p010 size as input data is random, DCT compression might not behave as expected
-        jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2);
-        auto jpegImgRaw = std::make_unique<uint8_t[]>(jpegImgR.maxLength);
-        jpegImgR.data = jpegImgRaw.get();
-
-//#define DUMP_PARAM
-#ifdef DUMP_PARAM
-        std::cout << "Api Select " << muxSwitch << std::endl;
-        std::cout << "image dimensions " << width << " x " << height << std::endl;
-        std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl;
-        std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl;
-        std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl;
-        std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl;
-        std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl;
-        std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl;
-        std::cout << "quality factor " << quality << std::endl;
-#endif
-
-        JpegR jpegHdr;
-        android::status_t status = android::UNKNOWN_ERROR;
-        if (muxSwitch == 0) { // api 0
-            jpegImgR.length = 0;
-            status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr);
-        } else if (muxSwitch == 1) { // api 1
-            jpegImgR.length = 0;
-            status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr);
-        } else {
-            // compressed img
-            JpegEncoderHelper encoder;
-            struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img;
-            if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width;
-            if (!yuv420ImgCopy.chroma_data) {
-                uint8_t* data = reinterpret_cast<uint8_t*>(yuv420Img.data);
-                yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height;
-                yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1;
-            }
-
-            if (encoder.compressImage(reinterpret_cast<uint8_t*>(yuv420ImgCopy.data),
-                                      reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data),
-                                      yuv420ImgCopy.width, yuv420ImgCopy.height,
-                                      yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride,
-                                      quality, nullptr, 0)) {
-                jpegImg.length = encoder.getCompressedImageSize();
-                jpegImg.maxLength = jpegImg.length;
-                jpegImg.data = encoder.getCompressedImagePtr();
-                jpegImg.colorGamut = yuv420Cg;
-
-                if (muxSwitch == 2) { // api 2
-                    jpegImgR.length = 0;
-                    status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR);
-                } else if (muxSwitch == 3) { // api 3
-                    jpegImgR.length = 0;
-                    status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR);
-                } else if (muxSwitch == 4) { // api 4
-                    jpegImgR.length = 0;
-                    JpegEncoderHelper gainMapEncoder;
-                    if (gainMapEncoder.compressImage(reinterpret_cast<uint8_t*>(grayImg.data),
-                                                     nullptr, grayImg.width, grayImg.height,
-                                                     grayImg.width, 0, quality, nullptr, 0)) {
-                        jpegGainMap.length = gainMapEncoder.getCompressedImageSize();
-                        jpegGainMap.maxLength = jpegImg.length;
-                        jpegGainMap.data = gainMapEncoder.getCompressedImagePtr();
-                        jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-                        ultrahdr_metadata_struct metadata;
-                        metadata.version = kJpegrVersion;
-                        if (tf == ULTRAHDR_TF_HLG) {
-                            metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits;
-                        } else if (tf == ULTRAHDR_TF_PQ) {
-                            metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits;
-                        } else {
-                            metadata.maxContentBoost = 1.0f;
-                        }
-                        metadata.minContentBoost = 1.0f;
-                        metadata.gamma = 1.0f;
-                        metadata.offsetSdr = 0.0f;
-                        metadata.offsetHdr = 0.0f;
-                        metadata.hdrCapacityMin = 1.0f;
-                        metadata.hdrCapacityMax = metadata.maxContentBoost;
-                        status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR);
-                    }
-                }
-            }
-        }
-        if (status == android::OK) {
-            std::vector<uint8_t> iccData(0);
-            std::vector<uint8_t> exifData(0);
-            jpegr_info_struct info{0, 0, &iccData, &exifData};
-            status = jpegHdr.getJPEGRInfo(&jpegImgR, &info);
-            if (status == android::OK) {
-                size_t outSize =
-                        info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
-                jpegr_uncompressed_struct decodedJpegR;
-                auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
-                decodedJpegR.data = decodedRaw.get();
-                ultrahdr_metadata_struct metadata;
-                jpegr_uncompressed_struct decodedGainMap{};
-                status = jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
-                                             mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX),
-                                             nullptr, of, &decodedGainMap, &metadata);
-                if (status != android::OK) {
-                    ALOGE("encountered error during decoding %d", status);
-                }
-                if (decodedGainMap.data) free(decodedGainMap.data);
-            } else {
-                ALOGE("encountered error during get jpeg info %d", status);
-            }
-        } else {
-            ALOGE("encountered error during encoding %d", status);
-        }
-    }
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    UltraHdrEncFuzzer fuzzHandle(data, size);
-    fuzzHandle.process();
-    return 0;
-}
diff --git a/libs/ultrahdr/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp
deleted file mode 100644
index ae9c4ca..0000000
--- a/libs/ultrahdr/gainmapmath.cpp
+++ /dev/null
@@ -1,775 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cmath>
-#include <vector>
-#include <ultrahdr/gainmapmath.h>
-
-namespace android::ultrahdr {
-
-static const std::vector<float> kPqOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
-      result.push_back(pqOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kPqInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
-      result.push_back(pqInvOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kHlgOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
-      result.push_back(hlgOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kHlgInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
-      result.push_back(hlgInvOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kSrgbInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
-      result.push_back(srgbInvOetf(value));
-    }
-    return result;
-}();
-
-// Use Shepard's method for inverse distance weighting. For more information:
-// en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
-
-float ShepardsIDW::euclideanDistance(float x1, float x2, float y1, float y2) {
-  return sqrt(((y2 - y1) * (y2 - y1)) + (x2 - x1) * (x2 - x1));
-}
-
-void ShepardsIDW::fillShepardsIDW(float *weights, int incR, int incB) {
-  for (int y = 0; y < mMapScaleFactor; y++) {
-    for (int x = 0; x < mMapScaleFactor; x++) {
-      float pos_x = ((float)x) / mMapScaleFactor;
-      float pos_y = ((float)y) / mMapScaleFactor;
-      int curr_x = floor(pos_x);
-      int curr_y = floor(pos_y);
-      int next_x = curr_x + incR;
-      int next_y = curr_y + incB;
-      float e1_distance = euclideanDistance(pos_x, curr_x, pos_y, curr_y);
-      int index = y * mMapScaleFactor * 4 + x * 4;
-      if (e1_distance == 0) {
-        weights[index++] = 1.f;
-        weights[index++] = 0.f;
-        weights[index++] = 0.f;
-        weights[index++] = 0.f;
-      } else {
-        float e1_weight = 1.f / e1_distance;
-
-        float e2_distance = euclideanDistance(pos_x, curr_x, pos_y, next_y);
-        float e2_weight = 1.f / e2_distance;
-
-        float e3_distance = euclideanDistance(pos_x, next_x, pos_y, curr_y);
-        float e3_weight = 1.f / e3_distance;
-
-        float e4_distance = euclideanDistance(pos_x, next_x, pos_y, next_y);
-        float e4_weight = 1.f / e4_distance;
-
-        float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
-
-        weights[index++] = e1_weight / total_weight;
-        weights[index++] = e2_weight / total_weight;
-        weights[index++] = e3_weight / total_weight;
-        weights[index++] = e4_weight / total_weight;
-      }
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// sRGB transformations
-
-static const float kMaxPixelFloat = 1.0f;
-static float clampPixelFloat(float value) {
-    return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
-}
-
-// See IEC 61966-2-1/Amd 1:2003, Equation F.7.
-static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
-
-float srgbLuminance(Color e) {
-  return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b;
-}
-
-// See ITU-R BT.709-6, Section 3.
-// Uses the same coefficients for deriving luma signal as
-// IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance
-// function above.
-static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f;
-
-Color srgbRgbToYuv(Color e_gamma) {
-  float y_gamma = srgbLuminance(e_gamma);
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kSrgbCb,
-             (e_gamma.r - y_gamma) / kSrgbCr }}};
-}
-
-// See ITU-R BT.709-6, Section 3.
-// Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we
-// can reuse the luminance coefficients since they are the same.
-static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG;
-static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG;
-
-Color srgbYuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u) }}};
-}
-
-// See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6.
-float srgbInvOetf(float e_gamma) {
-  if (e_gamma <= 0.04045f) {
-    return e_gamma / 12.92f;
-  } else {
-    return pow((e_gamma + 0.055f) / 1.055f, 2.4);
-  }
-}
-
-Color srgbInvOetf(Color e_gamma) {
-  return {{{ srgbInvOetf(e_gamma.r),
-             srgbInvOetf(e_gamma.g),
-             srgbInvOetf(e_gamma.b) }}};
-}
-
-// See IEC 61966-2-1, Equations F.5 and F.6.
-float srgbInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
-  return kSrgbInvOETF[value];
-}
-
-Color srgbInvOetfLUT(Color e_gamma) {
-  return {{{ srgbInvOetfLUT(e_gamma.r),
-             srgbInvOetfLUT(e_gamma.g),
-             srgbInvOetfLUT(e_gamma.b) }}};
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Display-P3 transformations
-
-// See SMPTE EG 432-1, Equation 7-8.
-static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;
-
-float p3Luminance(Color e) {
-  return kP3R * e.r + kP3G * e.g + kP3B * e.b;
-}
-
-// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
-// Unfortunately, calculation of luma signal differs from calculation of
-// luminance for Display-P3, so we can't reuse p3Luminance here.
-static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f;
-static const float kP3Cb = 1.772f, kP3Cr = 1.402f;
-
-Color p3RgbToYuv(Color e_gamma) {
-  float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b;
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kP3Cb,
-             (e_gamma.r - y_gamma) / kP3Cr }}};
-}
-
-// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
-// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must
-// use luma signal coefficients rather than the luminance coefficients.
-static const float kP3GCb = kP3YB * kP3Cb / kP3YG;
-static const float kP3GCr = kP3YR * kP3Cr / kP3YG;
-
-Color p3YuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}};
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-// BT.2100 transformations - according to ITU-R BT.2100-2
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF
-static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
-
-float bt2100Luminance(Color e) {
-  return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
-}
-
-// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
-// BT.2100 uses the same coefficients for calculating luma signal and luminance,
-// so we reuse the luminance function here.
-static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
-
-Color bt2100RgbToYuv(Color e_gamma) {
-  float y_gamma = bt2100Luminance(e_gamma);
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kBt2100Cb,
-             (e_gamma.r - y_gamma) / kBt2100Cr }}};
-}
-
-// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
-//
-// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients.
-//
-// Derived by inversing bt2100RgbToYuv. The derivation for R and B are  pretty
-// straight forward; we just invert the formulas for U and V above. But deriving
-// the formula for G is a bit more complicated:
-//
-// Start with equation for luminance:
-//   Y = kBt2100R * R + kBt2100G * G + kBt2100B * B
-// Solve for G:
-//   G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B
-// Substitute equations for R and B in terms YUV:
-//   G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B
-// Simplify:
-//   G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G)
-//     + U * (kBt2100B * kBt2100Cb / kBt2100G)
-//     + V * (kBt2100R * kBt2100Cr / kBt2100G)
-//
-// We then get the following coeficients for calculating G from YUV:
-//
-// Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1
-// Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645
-// Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713
-
-static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G;
-static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
-
-Color bt2100YuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}};
-}
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
-static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
-
-float hlgOetf(float e) {
-  if (e <= 1.0f/12.0f) {
-    return sqrt(3.0f * e);
-  } else {
-    return kHlgA * log(12.0f * e - kHlgB) + kHlgC;
-  }
-}
-
-Color hlgOetf(Color e) {
-  return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
-}
-
-float hlgOetfLUT(float e) {
-  uint32_t value = static_cast<uint32_t>(e * (kHlgOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
-
-  return kHlgOETF[value];
-}
-
-Color hlgOetfLUT(Color e) {
-  return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}};
-}
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
-float hlgInvOetf(float e_gamma) {
-  if (e_gamma <= 0.5f) {
-    return pow(e_gamma, 2.0f) / 3.0f;
-  } else {
-    return (exp((e_gamma - kHlgC) / kHlgA) + kHlgB) / 12.0f;
-  }
-}
-
-Color hlgInvOetf(Color e_gamma) {
-  return {{{ hlgInvOetf(e_gamma.r),
-             hlgInvOetf(e_gamma.g),
-             hlgInvOetf(e_gamma.b) }}};
-}
-
-float hlgInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
-
-  return kHlgInvOETF[value];
-}
-
-Color hlgInvOetfLUT(Color e_gamma) {
-  return {{{ hlgInvOetfLUT(e_gamma.r),
-             hlgInvOetfLUT(e_gamma.g),
-             hlgInvOetfLUT(e_gamma.b) }}};
-}
-
-// See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
-static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
-static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
-                   kPqC3 = 2392.0f / 4096.0f * 32.0f;
-
-float pqOetf(float e) {
-  if (e <= 0.0f) return 0.0f;
-  return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)),
-             kPqM2);
-}
-
-Color pqOetf(Color e) {
-  return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
-}
-
-float pqOetfLUT(float e) {
-  uint32_t value = static_cast<uint32_t>(e * (kPqOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kPqOETFNumEntries - 1);
-
-  return kPqOETF[value];
-}
-
-Color pqOetfLUT(Color e) {
-  return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}};
-}
-
-// Derived from the inverse of the Reference PQ OETF.
-static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
-                   kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
-
-float pqInvOetf(float e_gamma) {
-  // This equation blows up if e_gamma is 0.0, and checking on <= 0.0 doesn't
-  // always catch 0.0. So, check on 0.0001, since anything this small will
-  // effectively be crushed to zero anyways.
-  if (e_gamma <= 0.0001f) return 0.0f;
-  return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB)
-           / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)),
-             kPqInvE);
-}
-
-Color pqInvOetf(Color e_gamma) {
-  return {{{ pqInvOetf(e_gamma.r),
-             pqInvOetf(e_gamma.g),
-             pqInvOetf(e_gamma.b) }}};
-}
-
-float pqInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
-
-  return kPqInvOETF[value];
-}
-
-Color pqInvOetfLUT(Color e_gamma) {
-  return {{{ pqInvOetfLUT(e_gamma.r),
-             pqInvOetfLUT(e_gamma.g),
-             pqInvOetfLUT(e_gamma.b) }}};
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Color conversions
-
-Color bt709ToP3(Color e) {
- return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b,
-            0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b,
-            0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}};
-}
-
-Color bt709ToBt2100(Color e) {
- return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b,
-            0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b,
-            0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}};
-}
-
-Color p3ToBt709(Color e) {
- return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b,
-            -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b,
-            -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}};
-}
-
-Color p3ToBt2100(Color e) {
- return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b,
-            0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b,
-            -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}};
-}
-
-Color bt2100ToBt709(Color e) {
- return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b,
-            -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b,
-            -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}};
-}
-
-Color bt2100ToP3(Color e) {
- return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b,
-            -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b,
-            0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b
- }}};
-}
-
-// TODO: confirm we always want to convert like this before calculating
-// luminance.
-ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut,
-                                    ultrahdr_color_gamut hdr_gamut) {
-  switch (sdr_gamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return p3ToBt709;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return bt2100ToBt709;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return bt709ToP3;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return bt2100ToP3;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return bt709ToBt2100;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return p3ToBt2100;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      return nullptr;
-  }
-}
-
-// All of these conversions are derived from the respective input YUV->RGB conversion followed by
-// the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in this
-// file, given that we uses BT.709 encoding for sRGB and BT.601 encoding for Display-P3, to match
-// DataSpace.
-
-Color yuv709To601(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.101579f * e_gamma.u +  0.196076f * e_gamma.v,
-             0.0f * e_gamma.y +  0.989854f * e_gamma.u + -0.110653f * e_gamma.v,
-             0.0f * e_gamma.y + -0.072453f * e_gamma.u +  0.983398f * e_gamma.v }}};
-}
-
-Color yuv709To2100(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u +  0.096312f * e_gamma.v,
-             0.0f * e_gamma.y +  0.995306f * e_gamma.u + -0.051192f * e_gamma.v,
-             0.0f * e_gamma.y +  0.011507f * e_gamma.u +  1.002637f * e_gamma.v }}};
-}
-
-Color yuv601To709(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v,
-             0.0f * e_gamma.y +  1.018640f * e_gamma.u +  0.114618f * e_gamma.v,
-             0.0f * e_gamma.y +  0.075049f * e_gamma.u +  1.025327f * e_gamma.v }}};
-}
-
-Color yuv601To2100(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v,
-             0.0f * e_gamma.y +  1.010016f * e_gamma.u +  0.061592f * e_gamma.v,
-             0.0f * e_gamma.y +  0.086969f * e_gamma.u +  1.029350f * e_gamma.v }}};
-}
-
-Color yuv2100To709(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.018149f * e_gamma.u + -0.095132f * e_gamma.v,
-             0.0f * e_gamma.y +  1.004123f * e_gamma.u +  0.051267f * e_gamma.v,
-             0.0f * e_gamma.y + -0.011524f * e_gamma.u +  0.996782f * e_gamma.v }}};
-}
-
-Color yuv2100To601(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.117887f * e_gamma.u +  0.105521f * e_gamma.v,
-             0.0f * e_gamma.y +  0.995211f * e_gamma.u + -0.059549f * e_gamma.v,
-             0.0f * e_gamma.y + -0.084085f * e_gamma.u +  0.976518f * e_gamma.v }}};
-}
-
-void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
-                     ColorTransformFn fn) {
-  Color yuv1 = getYuv420Pixel(image, x_chroma * 2,     y_chroma * 2    );
-  Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2    );
-  Color yuv3 = getYuv420Pixel(image, x_chroma * 2,     y_chroma * 2 + 1);
-  Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1);
-
-  yuv1 = fn(yuv1);
-  yuv2 = fn(yuv2);
-  yuv3 = fn(yuv3);
-  yuv4 = fn(yuv4);
-
-  Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;
-
-  size_t pixel_y1_idx =  x_chroma * 2      +  y_chroma * 2      * image->luma_stride;
-  size_t pixel_y2_idx = (x_chroma * 2 + 1) +  y_chroma * 2      * image->luma_stride;
-  size_t pixel_y3_idx =  x_chroma * 2      + (y_chroma * 2 + 1) * image->luma_stride;
-  size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->luma_stride;
-
-  uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx];
-  uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y2_idx];
-  uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y3_idx];
-  uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y4_idx];
-
-  size_t pixel_count = image->chroma_stride * image->height / 2;
-  size_t pixel_uv_idx = x_chroma + y_chroma * (image->chroma_stride);
-
-  uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_uv_idx];
-  uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_count + pixel_uv_idx];
-
-  y1_uint = static_cast<uint8_t>(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255));
-  y2_uint = static_cast<uint8_t>(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255));
-  y3_uint = static_cast<uint8_t>(CLIP3((yuv3.y * 255.0f + 0.5f), 0, 255));
-  y4_uint = static_cast<uint8_t>(CLIP3((yuv4.y * 255.0f + 0.5f), 0, 255));
-
-  u_uint = static_cast<uint8_t>(CLIP3((new_uv.u * 255.0f + 128.0f + 0.5f), 0, 255));
-  v_uint = static_cast<uint8_t>(CLIP3((new_uv.v * 255.0f + 128.0f + 0.5f), 0, 255));
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Gain map calculations
-uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata) {
-  return encodeGain(y_sdr, y_hdr, metadata,
-                    log2(metadata->minContentBoost), log2(metadata->maxContentBoost));
-}
-
-uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
-                   float log2MinContentBoost, float log2MaxContentBoost) {
-  float gain = 1.0f;
-  if (y_sdr > 0.0f) {
-    gain = y_hdr / y_sdr;
-  }
-
-  if (gain < metadata->minContentBoost) gain = metadata->minContentBoost;
-  if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost;
-
-  return static_cast<uint8_t>((log2(gain) - log2MinContentBoost)
-                            / (log2MaxContentBoost - log2MinContentBoost)
-                            * 255.0f);
-}
-
-Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata) {
-  float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
-                 + log2(metadata->maxContentBoost) * gain;
-  float gainFactor = exp2(logBoost);
-  return e * gainFactor;
-}
-
-Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost) {
-  float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
-                 + log2(metadata->maxContentBoost) * gain;
-  float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost);
-  return e * gainFactor;
-}
-
-Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) {
-  float gainFactor = gainLUT.getGainFactor(gain);
-  return e * gainFactor;
-}
-
-Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->data);
-  size_t luma_stride = image->luma_stride;
-  uint8_t* chroma_data = reinterpret_cast<uint8_t*>(image->chroma_data);
-  size_t chroma_stride = image->chroma_stride;
-
-  size_t offset_cr = chroma_stride * (image->height / 2);
-  size_t pixel_y_idx = x + y * luma_stride;
-  size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride;
-
-  uint8_t y_uint = luma_data[pixel_y_idx];
-  uint8_t u_uint = chroma_data[pixel_chroma_idx];
-  uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx];
-
-  // 128 bias for UV given we are using jpeglib; see:
-  // https://github.com/kornelski/libjpeg/blob/master/structure.doc
-  return {{{ static_cast<float>(y_uint) / 255.0f,
-             (static_cast<float>(u_uint) - 128.0f) / 255.0f,
-             (static_cast<float>(v_uint) - 128.0f) / 255.0f }}};
-}
-
-Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->data);
-  size_t luma_stride = image->luma_stride == 0 ? image->width : image->luma_stride;
-  uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->chroma_data);
-  size_t chroma_stride = image->chroma_stride;
-
-  size_t pixel_y_idx = y * luma_stride + x;
-  size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1);
-  size_t pixel_v_idx = pixel_u_idx + 1;
-
-  uint16_t y_uint = luma_data[pixel_y_idx] >> 6;
-  uint16_t u_uint = chroma_data[pixel_u_idx] >> 6;
-  uint16_t v_uint = chroma_data[pixel_v_idx] >> 6;
-
-  // Conversions include taking narrow-range into account.
-  return {{{ (static_cast<float>(y_uint) - 64.0f) / 876.0f,
-             (static_cast<float>(u_uint) - 64.0f) / 896.0f - 0.5f,
-             (static_cast<float>(v_uint) - 64.0f) / 896.0f - 0.5f }}};
-}
-
-typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t);
-
-static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y,
-                          getPixelFn get_pixel_fn) {
-  Color e = {{{ 0.0f, 0.0f, 0.0f }}};
-  for (size_t dy = 0; dy < map_scale_factor; ++dy) {
-    for (size_t dx = 0; dx < map_scale_factor; ++dx) {
-      e += get_pixel_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy);
-    }
-  }
-
-  return e / static_cast<float>(map_scale_factor * map_scale_factor);
-}
-
-Color sampleYuv420(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
-  return samplePixels(image, map_scale_factor, x, y, getYuv420Pixel);
-}
-
-Color sampleP010(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
-  return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
-}
-
-// TODO: do we need something more clever for filtering either the map or images
-// to generate the map?
-
-static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
-  return val < low ? low : (high < val ? high : val);
-}
-
-static float mapUintToFloat(uint8_t map_uint) {
-  return static_cast<float>(map_uint) / 255.0f;
-}
-
-static float pythDistance(float x_diff, float y_diff) {
-  return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f));
-}
-
-// TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
-float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y) {
-  float x_map = static_cast<float>(x) / map_scale_factor;
-  float y_map = static_cast<float>(y) / map_scale_factor;
-
-  size_t x_lower = static_cast<size_t>(floor(x_map));
-  size_t x_upper = x_lower + 1;
-  size_t y_lower = static_cast<size_t>(floor(y_map));
-  size_t y_upper = y_lower + 1;
-
-  x_lower = clamp(x_lower, 0, map->width - 1);
-  x_upper = clamp(x_upper, 0, map->width - 1);
-  y_lower = clamp(y_lower, 0, map->height - 1);
-  y_upper = clamp(y_upper, 0, map->height - 1);
-
-  // Use Shepard's method for inverse distance weighting. For more information:
-  // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
-
-  float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
-  float e1_dist = pythDistance(x_map - static_cast<float>(x_lower),
-                               y_map - static_cast<float>(y_lower));
-  if (e1_dist == 0.0f) return e1;
-
-  float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
-  float e2_dist = pythDistance(x_map - static_cast<float>(x_lower),
-                               y_map - static_cast<float>(y_upper));
-  if (e2_dist == 0.0f) return e2;
-
-  float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
-  float e3_dist = pythDistance(x_map - static_cast<float>(x_upper),
-                               y_map - static_cast<float>(y_lower));
-  if (e3_dist == 0.0f) return e3;
-
-  float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
-  float e4_dist = pythDistance(x_map - static_cast<float>(x_upper),
-                               y_map - static_cast<float>(y_upper));
-  if (e4_dist == 0.0f) return e2;
-
-  float e1_weight = 1.0f / e1_dist;
-  float e2_weight = 1.0f / e2_dist;
-  float e3_weight = 1.0f / e3_dist;
-  float e4_weight = 1.0f / e4_dist;
-  float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
-
-  return e1 * (e1_weight / total_weight)
-       + e2 * (e2_weight / total_weight)
-       + e3 * (e3_weight / total_weight)
-       + e4 * (e4_weight / total_weight);
-}
-
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
-                ShepardsIDW& weightTables) {
-  // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
-  // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor)
-  int x_lower = x / map_scale_factor;
-  int x_upper = x_lower + 1;
-  int y_lower = y / map_scale_factor;
-  int y_upper = y_lower + 1;
-
-  x_lower = std::min(x_lower, map->width - 1);
-  x_upper = std::min(x_upper, map->width - 1);
-  y_lower = std::min(y_lower, map->height - 1);
-  y_upper = std::min(y_upper, map->height - 1);
-
-  float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
-  float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
-  float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
-  float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
-
-  // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
-  // following by using & (map_scale_factor - 1)
-  int offset_x = x % map_scale_factor;
-  int offset_y = y % map_scale_factor;
-
-  float* weights = weightTables.mWeights;
-  if (x_lower == x_upper && y_lower == y_upper) weights = weightTables.mWeightsC;
-  else if (x_lower == x_upper) weights = weightTables.mWeightsNR;
-  else if (y_lower == y_upper) weights = weightTables.mWeightsNB;
-  weights += offset_y * map_scale_factor * 4 + offset_x * 4;
-
-  return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3];
-}
-
-uint32_t colorToRgba1010102(Color e_gamma) {
-  return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f))
-       | ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
-       | ((0x3ff & static_cast<uint32_t>(e_gamma.b * 1023.0f)) << 20)
-       | (0x3 << 30);  // Set alpha to 1.0
-}
-
-uint64_t colorToRgbaF16(Color e_gamma) {
-  return (uint64_t) floatToHalf(e_gamma.r)
-       | (((uint64_t) floatToHalf(e_gamma.g)) << 16)
-       | (((uint64_t) floatToHalf(e_gamma.b)) << 32)
-       | (((uint64_t) floatToHalf(1.0f)) << 48);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp
deleted file mode 100644
index e41b645..0000000
--- a/libs/ultrahdr/icc.cpp
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-#include <ultrahdr/icc.h>
-#include <vector>
-#include <utils/Log.h>
-
-#ifndef FLT_MAX
-#define FLT_MAX 0x1.fffffep127f
-#endif
-
-namespace android::ultrahdr {
-static void Matrix3x3_apply(const Matrix3x3* m, float* x) {
-    float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2];
-    float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2];
-    float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2];
-    x[0] = y0;
-    x[1] = y1;
-    x[2] = y2;
-}
-
-bool Matrix3x3_invert(const Matrix3x3* src, Matrix3x3* dst) {
-    double a00 = src->vals[0][0],
-           a01 = src->vals[1][0],
-           a02 = src->vals[2][0],
-           a10 = src->vals[0][1],
-           a11 = src->vals[1][1],
-           a12 = src->vals[2][1],
-           a20 = src->vals[0][2],
-           a21 = src->vals[1][2],
-           a22 = src->vals[2][2];
-
-    double b0 = a00*a11 - a01*a10,
-           b1 = a00*a12 - a02*a10,
-           b2 = a01*a12 - a02*a11,
-           b3 = a20,
-           b4 = a21,
-           b5 = a22;
-
-    double determinant = b0*b5
-                       - b1*b4
-                       + b2*b3;
-
-    if (determinant == 0) {
-        return false;
-    }
-
-    double invdet = 1.0 / determinant;
-    if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
-        return false;
-    }
-
-    b0 *= invdet;
-    b1 *= invdet;
-    b2 *= invdet;
-    b3 *= invdet;
-    b4 *= invdet;
-    b5 *= invdet;
-
-    dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
-    dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
-    dst->vals[2][0] = (float)(        +     b2 );
-    dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
-    dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
-    dst->vals[2][1] = (float)(        -     b1 );
-    dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
-    dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
-    dst->vals[2][2] = (float)(        +     b0 );
-
-    for (int r = 0; r < 3; ++r)
-    for (int c = 0; c < 3; ++c) {
-        if (!isfinitef_(dst->vals[r][c])) {
-            return false;
-        }
-    }
-    return true;
-}
-
-static Matrix3x3 Matrix3x3_concat(const Matrix3x3* A, const Matrix3x3* B) {
-    Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
-    for (int r = 0; r < 3; r++)
-        for (int c = 0; c < 3; c++) {
-            m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
-                         + A->vals[r][1] * B->vals[1][c]
-                         + A->vals[r][2] * B->vals[2][c];
-        }
-    return m;
-}
-
-static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_lab) {
-    float v[3] = {
-            xyz_float[0] / kD50_x,
-            xyz_float[1] / kD50_y,
-            xyz_float[2] / kD50_z,
-    };
-    for (size_t i = 0; i < 3; ++i) {
-        v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f);
-    }
-    const float L = v[1] * 116.0f - 16.0f;
-    const float a = (v[0] - v[1]) * 500.0f;
-    const float b = (v[1] - v[2]) * 200.0f;
-    const float Lab_unorm[3] = {
-            L * (1 / 100.f),
-            (a + 128.0f) * (1 / 255.0f),
-            (b + 128.0f) * (1 / 255.0f),
-    };
-    // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the
-    // table, but the spec appears to indicate that the value should be 0xFF00.
-    // https://crbug.com/skia/13807
-    for (size_t i = 0; i < 3; ++i) {
-        reinterpret_cast<uint16_t*>(grid16_lab)[i] =
-                Endian_SwapBE16(float_round_to_unorm16(Lab_unorm[i]));
-    }
-}
-
-std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf,
-                                       const ultrahdr_color_gamut gamut) {
-    std::string result;
-    switch (gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-            result += "sRGB";
-            break;
-        case ULTRAHDR_COLORGAMUT_P3:
-            result += "Display P3";
-            break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-            result += "Rec2020";
-            break;
-        default:
-            result += "Unknown";
-            break;
-    }
-    result += " Gamut with ";
-    switch (tf) {
-        case ULTRAHDR_TF_SRGB:
-            result += "sRGB";
-            break;
-        case ULTRAHDR_TF_LINEAR:
-            result += "Linear";
-            break;
-        case ULTRAHDR_TF_PQ:
-            result += "PQ";
-            break;
-        case ULTRAHDR_TF_HLG:
-            result += "HLG";
-            break;
-        default:
-            result += "Unknown";
-            break;
-    }
-    result += " Transfer";
-    return result;
-}
-
-sp<DataStruct> IccHelper::write_text_tag(const char* text) {
-    uint32_t text_length = strlen(text);
-    uint32_t header[] = {
-            Endian_SwapBE32(kTAG_TextType),                         // Type signature
-            0,                                                      // Reserved
-            Endian_SwapBE32(1),                                     // Number of records
-            Endian_SwapBE32(12),                                    // Record size (must be 12)
-            Endian_SwapBE32(SetFourByteTag('e', 'n', 'U', 'S')),    // English USA
-            Endian_SwapBE32(2 * text_length),                       // Length of string in bytes
-            Endian_SwapBE32(28),                                    // Offset of string
-    };
-
-    uint32_t total_length = text_length * 2 + sizeof(header);
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-
-    if (!dataStruct->write(header, sizeof(header))) {
-        ALOGE("write_text_tag(): error in writing data");
-        return dataStruct;
-    }
-
-    for (size_t i = 0; i < text_length; i++) {
-        // Convert ASCII to big-endian UTF-16.
-        dataStruct->write8(0);
-        dataStruct->write8(text[i]);
-    }
-
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_xyz_tag(float x, float y, float z) {
-    uint32_t data[] = {
-            Endian_SwapBE32(kXYZ_PCSSpace),
-            0,
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(x))),
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))),
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))),
-    };
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(sizeof(data));
-    dataStruct->write(&data, sizeof(data));
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_trc_tag(const int table_entries, const void* table_16) {
-    int total_length = 4 + 4 + 4 + table_entries * 2;
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_CurveType));     // Type
-    dataStruct->write32(0);                                   // Reserved
-    dataStruct->write32(Endian_SwapBE32(table_entries));      // Value count
-    for (size_t i = 0; i < table_entries; ++i) {
-        uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i];
-        dataStruct->write16(value);
-    }
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_trc_tag(const TransferFunction& fn) {
-    if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f
-            && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) {
-        int total_length = 16;
-        sp<DataStruct> dataStruct = new DataStruct(total_length);
-        dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
-        dataStruct->write32(0);                                    // Reserved
-        dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
-        dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
-        return dataStruct;
-    }
-
-    int total_length = 40;
-    sp<DataStruct> dataStruct = new DataStruct(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
-    dataStruct->write32(0);                                    // Reserved
-    dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f)));
-    return dataStruct;
-}
-
-float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) {
-    if (L <= 0.f) {
-        return 1.f;
-    }
-    if (tf == ULTRAHDR_TF_PQ) {
-        // The PQ transfer function will map to the range [0, 1]. Linearly scale
-        // it up to the range [0, 10,000/203]. We will then tone map that back
-        // down to [0, 1].
-        constexpr float kInputMaxLuminance = 10000 / 203.f;
-        constexpr float kOutputMaxLuminance = 1.0;
-        L *= kInputMaxLuminance;
-
-        // Compute the tone map gain which will tone map from 10,000/203 to 1.0.
-        constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance);
-        constexpr float kToneMapB = 1.f / kOutputMaxLuminance;
-        return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L);
-    }
-    if (tf == ULTRAHDR_TF_HLG) {
-        // Let Lw be the brightness of the display in nits.
-        constexpr float Lw = 203.f;
-        const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f);
-        return std::pow(L, gamma - 1.f);
-    }
-    return 1.f;
-}
-
-sp<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries,
-                                         uint32_t transfer_characteristics) {
-    int total_length = 12;  // 4 + 4 + 1 + 1 + 1 + 1
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_cicp));    // Type signature
-    dataStruct->write32(0);                             // Reserved
-    dataStruct->write8(color_primaries);                // Color primaries
-    dataStruct->write8(transfer_characteristics);       // Transfer characteristics
-    dataStruct->write8(0);                              // RGB matrix
-    dataStruct->write8(1);                              // Full range
-    return dataStruct;
-}
-
-void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) {
-    // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50.
-    Matrix3x3 src_to_rec2020;
-    const Matrix3x3 rec2020_to_XYZD50 = kRec2020;
-    {
-        Matrix3x3 XYZD50_to_rec2020;
-        Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020);
-        src_to_rec2020 = Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50);
-    }
-
-    // Convert the source signal to linear.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        rgb[i] = pqOetf(rgb[i]);
-    }
-
-    // Convert source gamut to Rec2020.
-    Matrix3x3_apply(&src_to_rec2020, rgb);
-
-    // Compute the luminance of the signal.
-    float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}});
-
-    // Compute the tone map gain based on the luminance.
-    float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L);
-
-    // Apply the tone map gain.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        rgb[i] *= tone_map_gain;
-    }
-
-    // Convert from Rec2020-linear to XYZD50.
-    Matrix3x3_apply(&rec2020_to_XYZD50, rgb);
-}
-
-sp<DataStruct> IccHelper::write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
-    uint32_t value_count = kNumChannels;
-    for (uint32_t i = 0; i < kNumChannels; ++i) {
-        value_count *= grid_points[i];
-    }
-
-    int total_length = 20 + 2 * value_count;
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-
-    for (size_t i = 0; i < 16; ++i) {
-        dataStruct->write8(i < kNumChannels ? grid_points[i] : 0);  // Grid size
-    }
-    dataStruct->write8(2);  // Grid byte width (always 16-bit)
-    dataStruct->write8(0);  // Reserved
-    dataStruct->write8(0);  // Reserved
-    dataStruct->write8(0);  // Reserved
-
-    for (uint32_t i = 0; i < value_count; ++i) {
-        uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i];
-        dataStruct->write16(value);
-    }
-
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type,
-                                               bool has_a_curves,
-                                               const uint8_t* grid_points,
-                                               const uint8_t* grid_16) {
-    const size_t b_curves_offset = 32;
-    sp<DataStruct> b_curves_data[kNumChannels];
-    sp<DataStruct> a_curves_data[kNumChannels];
-    size_t clut_offset = 0;
-    sp<DataStruct> clut;
-    size_t a_curves_offset = 0;
-
-    // The "B" curve is required.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        b_curves_data[i] = write_trc_tag(kLinear_TransFun);
-    }
-
-    // The "A" curve and CLUT are optional.
-    if (has_a_curves) {
-        clut_offset = b_curves_offset;
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            clut_offset += b_curves_data[i]->getLength();
-        }
-        clut = write_clut(grid_points, grid_16);
-
-        a_curves_offset = clut_offset + clut->getLength();
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            a_curves_data[i] = write_trc_tag(kLinear_TransFun);
-        }
-    }
-
-    int total_length = b_curves_offset;
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        total_length += b_curves_data[i]->getLength();
-    }
-    if (has_a_curves) {
-        total_length += clut->getLength();
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            total_length += a_curves_data[i]->getLength();
-        }
-    }
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(type));             // Type signature
-    dataStruct->write32(0);                                 // Reserved
-    dataStruct->write8(kNumChannels);                       // Input channels
-    dataStruct->write8(kNumChannels);                       // Output channels
-    dataStruct->write16(0);                                 // Reserved
-    dataStruct->write32(Endian_SwapBE32(b_curves_offset));  // B curve offset
-    dataStruct->write32(Endian_SwapBE32(0));                // Matrix offset (ignored)
-    dataStruct->write32(Endian_SwapBE32(0));                // M curve offset (ignored)
-    dataStruct->write32(Endian_SwapBE32(clut_offset));      // CLUT offset
-    dataStruct->write32(Endian_SwapBE32(a_curves_offset));  // A curve offset
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        if (dataStruct->write(b_curves_data[i]->getData(), b_curves_data[i]->getLength())) {
-            return dataStruct;
-        }
-    }
-    if (has_a_curves) {
-        dataStruct->write(clut->getData(), clut->getLength());
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            dataStruct->write(a_curves_data[i]->getData(), a_curves_data[i]->getLength());
-        }
-    }
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
-                                          ultrahdr_color_gamut gamut) {
-    ICCHeader header;
-
-    std::vector<std::pair<uint32_t, sp<DataStruct>>> tags;
-
-    // Compute profile description tag
-    std::string desc = get_desc_string(tf, gamut);
-
-    tags.emplace_back(kTAG_desc, write_text_tag(desc.c_str()));
-
-    Matrix3x3 toXYZD50;
-    switch (gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-            toXYZD50 = kSRGB;
-            break;
-        case ULTRAHDR_COLORGAMUT_P3:
-            toXYZD50 = kDisplayP3;
-            break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-            toXYZD50 = kRec2020;
-            break;
-        default:
-            // Should not fall here.
-            return nullptr;
-    }
-
-    // Compute primaries.
-    {
-        tags.emplace_back(kTAG_rXYZ,
-                write_xyz_tag(toXYZD50.vals[0][0], toXYZD50.vals[1][0], toXYZD50.vals[2][0]));
-        tags.emplace_back(kTAG_gXYZ,
-                write_xyz_tag(toXYZD50.vals[0][1], toXYZD50.vals[1][1], toXYZD50.vals[2][1]));
-        tags.emplace_back(kTAG_bXYZ,
-                write_xyz_tag(toXYZD50.vals[0][2], toXYZD50.vals[1][2], toXYZD50.vals[2][2]));
-    }
-
-    // Compute white point tag (must be D50)
-    tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
-
-    // Compute transfer curves.
-    if (tf != ULTRAHDR_TF_PQ) {
-        if (tf == ULTRAHDR_TF_HLG) {
-            std::vector<uint8_t> trc_table;
-            trc_table.resize(kTrcTableSize * 2);
-            for (uint32_t i = 0; i < kTrcTableSize; ++i) {
-                float x = i / (kTrcTableSize - 1.f);
-                float y = hlgOetf(x);
-                y *= compute_tone_map_gain(tf, y);
-                float_to_table16(y, &trc_table[2 * i]);
-            }
-
-            tags.emplace_back(kTAG_rTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-            tags.emplace_back(kTAG_gTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-            tags.emplace_back(kTAG_bTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-        } else {
-            tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun));
-            tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun));
-            tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun));
-        }
-    }
-
-    // Compute CICP.
-    if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) {
-        // The CICP tag is present in ICC 4.4, so update the header's version.
-        header.version = Endian_SwapBE32(0x04400000);
-
-        uint32_t color_primaries = 0;
-        if (gamut == ULTRAHDR_COLORGAMUT_BT709) {
-            color_primaries = kCICPPrimariesSRGB;
-        } else if (gamut == ULTRAHDR_COLORGAMUT_P3) {
-            color_primaries = kCICPPrimariesP3;
-        }
-
-        uint32_t transfer_characteristics = 0;
-        if (tf == ULTRAHDR_TF_SRGB) {
-            transfer_characteristics = kCICPTrfnSRGB;
-        } else if (tf == ULTRAHDR_TF_LINEAR) {
-            transfer_characteristics = kCICPTrfnLinear;
-        } else if (tf == ULTRAHDR_TF_PQ) {
-            transfer_characteristics = kCICPTrfnPQ;
-        } else if (tf == ULTRAHDR_TF_HLG) {
-            transfer_characteristics = kCICPTrfnHLG;
-        }
-        tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics));
-    }
-
-    // Compute A2B0.
-    if (tf == ULTRAHDR_TF_PQ) {
-        std::vector<uint8_t> a2b_grid;
-        a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2);
-        size_t a2b_grid_index = 0;
-        for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
-            for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
-                for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
-                    float rgb[3] = {
-                            r_index / (kGridSize - 1.f),
-                            g_index / (kGridSize - 1.f),
-                            b_index / (kGridSize - 1.f),
-                    };
-                    compute_lut_entry(toXYZD50, rgb);
-                    float_XYZD50_to_grid16_lab(rgb, &a2b_grid[a2b_grid_index]);
-                    a2b_grid_index += 6;
-                }
-            }
-        }
-        const uint8_t* grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
-
-        uint8_t grid_points[kNumChannels];
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            grid_points[i] = kGridSize;
-        }
-
-        auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
-                                             /* has_a_curves */ true,
-                                             grid_points,
-                                             grid_16);
-        tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
-    }
-
-    // Compute B2A0.
-    if (tf == ULTRAHDR_TF_PQ) {
-        auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
-                                             /* has_a_curves */ false,
-                                             /* grid_points */ nullptr,
-                                             /* grid_16 */ nullptr);
-        tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
-    }
-
-    // Compute copyright tag
-    tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2022"));
-
-    // Compute the size of the profile.
-    size_t tag_data_size = 0;
-    for (const auto& tag : tags) {
-        tag_data_size += tag.second->getLength();
-    }
-    size_t tag_table_size = kICCTagTableEntrySize * tags.size();
-    size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
-
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(profile_size + kICCIdentifierSize);
-
-    // Write identifier, chunk count, and chunk ID
-    if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) ||
-        !dataStruct->write8(1) || !dataStruct->write8(1)) {
-        ALOGE("writeIccProfile(): error in identifier");
-        return dataStruct;
-    }
-
-    // Write the header.
-    header.data_color_space = Endian_SwapBE32(Signature_RGB);
-    header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ);
-    header.size = Endian_SwapBE32(profile_size);
-    header.tag_count = Endian_SwapBE32(tags.size());
-
-    if (!dataStruct->write(&header, sizeof(header))) {
-        ALOGE("writeIccProfile(): error in header");
-        return dataStruct;
-    }
-
-    // Write the tag table. Track the offset and size of the previous tag to
-    // compute each tag's offset. An empty SkData indicates that the previous
-    // tag is to be reused.
-    uint32_t last_tag_offset = sizeof(header) + tag_table_size;
-    uint32_t last_tag_size = 0;
-    for (const auto& tag : tags) {
-        last_tag_offset = last_tag_offset + last_tag_size;
-        last_tag_size = tag.second->getLength();
-        uint32_t tag_table_entry[3] = {
-                Endian_SwapBE32(tag.first),
-                Endian_SwapBE32(last_tag_offset),
-                Endian_SwapBE32(last_tag_size),
-        };
-        if (!dataStruct->write(tag_table_entry, sizeof(tag_table_entry))) {
-            ALOGE("writeIccProfile(): error in writing tag table");
-            return dataStruct;
-        }
-    }
-
-    // Write the tags.
-    for (const auto& tag : tags) {
-        if (!dataStruct->write(tag.second->getData(), tag.second->getLength())) {
-            ALOGE("writeIccProfile(): error in writing tags");
-            return dataStruct;
-        }
-    }
-
-    return dataStruct;
-}
-
-bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix,
-                                  const uint8_t* red_tag,
-                                  const uint8_t* green_tag,
-                                  const uint8_t* blue_tag) {
-    sp<DataStruct> red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0],
-                                                matrix.vals[2][0]);
-    sp<DataStruct> green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1],
-                                                  matrix.vals[2][1]);
-    sp<DataStruct> blue_tag_test = write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2],
-                                                 matrix.vals[2][2]);
-    return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 &&
-           memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 &&
-           memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0;
-}
-
-ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) {
-    // Each tag table entry consists of 3 fields of 4 bytes each.
-    static const size_t kTagTableEntrySize = 12;
-
-    if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc_data) + kICCIdentifierSize;
-
-    ICCHeader* header = reinterpret_cast<ICCHeader*>(icc_bytes);
-
-    // Use 0 to indicate not found, since offsets are always relative to start
-    // of ICC data and therefore a tag offset of zero would never be valid.
-    size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0;
-    size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0;
-    for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) {
-        uint32_t* tag_entry_start = reinterpret_cast<uint32_t*>(
-            icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize);
-        // first 4 bytes are the tag signature, next 4 bytes are the tag offset,
-        // last 4 bytes are the tag length in bytes.
-        if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) {
-            red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            red_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) {
-            green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            green_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) {
-            blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        }
-    }
-
-    if (red_primary_offset == 0 || red_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size ||
-        green_primary_offset == 0 || green_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size ||
-        blue_primary_offset == 0 || blue_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    uint8_t* red_tag = icc_bytes + red_primary_offset;
-    uint8_t* green_tag = icc_bytes + green_primary_offset;
-    uint8_t* blue_tag = icc_bytes + blue_primary_offset;
-
-    // Serialize tags as we do on encode and compare what we find to that to
-    // determine the gamut (since we don't have a need yet for full deserialize).
-    if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_BT709;
-    } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_P3;
-    } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_BT2100;
-    }
-
-    // Didn't find a match to one of the profiles we write; indicate the gamut
-    // is unspecified since we don't understand it.
-    return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
deleted file mode 100644
index 9f1238f..0000000
--- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h
+++ /dev/null
@@ -1,505 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
-#define ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
-
-#include <cmath>
-#include <stdint.h>
-
-#include <ultrahdr/jpegr.h>
-
-namespace android::ultrahdr {
-
-#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
-
-////////////////////////////////////////////////////////////////////////////////
-// Framework
-
-const float kSdrWhiteNits = 100.0f;
-const float kHlgMaxNits = 1000.0f;
-const float kPqMaxNits = 10000.0f;
-
-struct Color {
-  union {
-    struct {
-      float r;
-      float g;
-      float b;
-    };
-    struct {
-      float y;
-      float u;
-      float v;
-    };
-  };
-};
-
-typedef Color (*ColorTransformFn)(Color);
-typedef float (*ColorCalculationFn)(Color);
-
-// A transfer function mapping encoded values to linear values,
-// represented by this 7-parameter piecewise function:
-//
-//   linear = sign(encoded) *  (c*|encoded| + f)       , 0 <= |encoded| < d
-//          = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded|
-//
-// (A simple gamma transfer function sets g to gamma and a to 1.)
-typedef struct TransferFunction {
-    float g, a,b,c,d,e,f;
-} TransferFunction;
-
-static constexpr TransferFunction kSRGB_TransFun =
-    { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f };
-
-static constexpr TransferFunction kLinear_TransFun =
-    { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
-
-inline Color operator+=(Color& lhs, const Color& rhs) {
-  lhs.r += rhs.r;
-  lhs.g += rhs.g;
-  lhs.b += rhs.b;
-  return lhs;
-}
-inline Color operator-=(Color& lhs, const Color& rhs) {
-  lhs.r -= rhs.r;
-  lhs.g -= rhs.g;
-  lhs.b -= rhs.b;
-  return lhs;
-}
-
-inline Color operator+(const Color& lhs, const Color& rhs) {
-  Color temp = lhs;
-  return temp += rhs;
-}
-inline Color operator-(const Color& lhs, const Color& rhs) {
-  Color temp = lhs;
-  return temp -= rhs;
-}
-
-inline Color operator+=(Color& lhs, const float rhs) {
-  lhs.r += rhs;
-  lhs.g += rhs;
-  lhs.b += rhs;
-  return lhs;
-}
-inline Color operator-=(Color& lhs, const float rhs) {
-  lhs.r -= rhs;
-  lhs.g -= rhs;
-  lhs.b -= rhs;
-  return lhs;
-}
-inline Color operator*=(Color& lhs, const float rhs) {
-  lhs.r *= rhs;
-  lhs.g *= rhs;
-  lhs.b *= rhs;
-  return lhs;
-}
-inline Color operator/=(Color& lhs, const float rhs) {
-  lhs.r /= rhs;
-  lhs.g /= rhs;
-  lhs.b /= rhs;
-  return lhs;
-}
-
-inline Color operator+(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp += rhs;
-}
-inline Color operator-(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp -= rhs;
-}
-inline Color operator*(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp *= rhs;
-}
-inline Color operator/(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp /= rhs;
-}
-
-inline uint16_t floatToHalf(float f) {
-  // round-to-nearest-even: add last bit after truncated mantissa
-  const uint32_t b = *((uint32_t*)&f) + 0x00001000;
-
-  const uint32_t e = (b & 0x7F800000) >> 23; // exponent
-  const uint32_t m = b & 0x007FFFFF; // mantissa
-
-  // sign : normalized : denormalized : saturate
-  return (b & 0x80000000) >> 16
-            | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13)
-            | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1)
-            | (e > 143) * 0x7FFF;
-}
-
-constexpr size_t kGainFactorPrecision = 10;
-constexpr size_t kGainFactorNumEntries = 1 << kGainFactorPrecision;
-struct GainLUT {
-  GainLUT(ultrahdr_metadata_ptr metadata) {
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
-                     + log2(metadata->maxContentBoost) * value;
-      mGainTable[idx] = exp2(logBoost);
-    }
-  }
-
-  GainLUT(ultrahdr_metadata_ptr metadata, float displayBoost) {
-    float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f;
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
-                     + log2(metadata->maxContentBoost) * value;
-      mGainTable[idx] = exp2(logBoost * boostFactor);
-    }
-  }
-
-  ~GainLUT() {
-  }
-
-  float getGainFactor(float gain) {
-    uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1) + 0.5);
-    //TODO() : Remove once conversion modules have appropriate clamping in place
-    idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
-    return mGainTable[idx];
-  }
-
-private:
-  float mGainTable[kGainFactorNumEntries];
-};
-
-struct ShepardsIDW {
-  ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} {
-    const int size = mMapScaleFactor * mMapScaleFactor * 4;
-    mWeights = new float[size];
-    mWeightsNR = new float[size];
-    mWeightsNB = new float[size];
-    mWeightsC = new float[size];
-    fillShepardsIDW(mWeights, 1, 1);
-    fillShepardsIDW(mWeightsNR, 0, 1);
-    fillShepardsIDW(mWeightsNB, 1, 0);
-    fillShepardsIDW(mWeightsC, 0, 0);
-  }
-  ~ShepardsIDW() {
-    delete[] mWeights;
-    delete[] mWeightsNR;
-    delete[] mWeightsNB;
-    delete[] mWeightsC;
-  }
-
-  int mMapScaleFactor;
-  // Image :-
-  // p00 p01 p02 p03 p04 p05 p06 p07
-  // p10 p11 p12 p13 p14 p15 p16 p17
-  // p20 p21 p22 p23 p24 p25 p26 p27
-  // p30 p31 p32 p33 p34 p35 p36 p37
-  // p40 p41 p42 p43 p44 p45 p46 p47
-  // p50 p51 p52 p53 p54 p55 p56 p57
-  // p60 p61 p62 p63 p64 p65 p66 p67
-  // p70 p71 p72 p73 p74 p75 p76 p77
-
-  // Gain Map (for 4 scale factor) :-
-  // m00 p01
-  // m10 m11
-
-  // Gain sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
-  // reconstruction. hence table weight size is 4.
-  float* mWeights;
-  // TODO: check if its ok to mWeights at places
-  float* mWeightsNR;  // no right
-  float* mWeightsNB;  // no bottom
-  float* mWeightsC;  // no right & bottom
-
-  float euclideanDistance(float x1, float x2, float y1, float y2);
-  void fillShepardsIDW(float *weights, int incR, int incB);
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// sRGB transformations
-// NOTE: sRGB has the same color primaries as BT.709, but different transfer
-// function. For this reason, all sRGB transformations here apply to BT.709,
-// except for those concerning transfer functions.
-
-/*
- * Calculate the luminance of a linear RGB sRGB pixel, according to
- * IEC 61966-2-1/Amd 1:2003.
- *
- * [0.0, 1.0] range in and out.
- */
-float srgbLuminance(Color e);
-
-/*
- * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6.
- *
- * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color srgbRgbToYuv(Color e_gamma);
-
-
-/*
- * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6.
- *
- * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color srgbYuvToRgb(Color e_gamma);
-
-/*
- * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003.
- *
- * [0.0, 1.0] range in and out.
- */
-float srgbInvOetf(float e_gamma);
-Color srgbInvOetf(Color e_gamma);
-float srgbInvOetfLUT(float e_gamma);
-Color srgbInvOetfLUT(Color e_gamma);
-
-constexpr size_t kSrgbInvOETFPrecision = 10;
-constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision;
-
-////////////////////////////////////////////////////////////////////////////////
-// Display-P3 transformations
-
-/*
- * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1.
- *
- * [0.0, 1.0] range in and out.
- */
-float p3Luminance(Color e);
-
-/*
- * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7.
- *
- * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color p3RgbToYuv(Color e_gamma);
-
-/*
- * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7.
- *
- * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color p3YuvToRgb(Color e_gamma);
-
-
-////////////////////////////////////////////////////////////////////////////////
-// BT.2100 transformations - according to ITU-R BT.2100-2
-
-/*
- * Calculate the luminance of a linear RGB BT.2100 pixel.
- *
- * [0.0, 1.0] range in and out.
- */
-float bt2100Luminance(Color e);
-
-/*
- * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2.
- *
- * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color bt2100RgbToYuv(Color e_gamma);
-
-/*
- * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2.
- *
- * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color bt2100YuvToRgb(Color e_gamma);
-
-/*
- * Convert from scene luminance to HLG.
- *
- * [0.0, 1.0] range in and out.
- */
-float hlgOetf(float e);
-Color hlgOetf(Color e);
-float hlgOetfLUT(float e);
-Color hlgOetfLUT(Color e);
-
-constexpr size_t kHlgOETFPrecision = 16;
-constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
-
-/*
- * Convert from HLG to scene luminance.
- *
- * [0.0, 1.0] range in and out.
- */
-float hlgInvOetf(float e_gamma);
-Color hlgInvOetf(Color e_gamma);
-float hlgInvOetfLUT(float e_gamma);
-Color hlgInvOetfLUT(Color e_gamma);
-
-constexpr size_t kHlgInvOETFPrecision = 12;
-constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
-
-/*
- * Convert from scene luminance to PQ.
- *
- * [0.0, 1.0] range in and out.
- */
-float pqOetf(float e);
-Color pqOetf(Color e);
-float pqOetfLUT(float e);
-Color pqOetfLUT(Color e);
-
-constexpr size_t kPqOETFPrecision = 16;
-constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
-
-/*
- * Convert from PQ to scene luminance in nits.
- *
- * [0.0, 1.0] range in and out.
- */
-float pqInvOetf(float e_gamma);
-Color pqInvOetf(Color e_gamma);
-float pqInvOetfLUT(float e_gamma);
-Color pqInvOetfLUT(Color e_gamma);
-
-constexpr size_t kPqInvOETFPrecision = 12;
-constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Color space conversions
-
-/*
- * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1.
- *
- * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the
- * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is
- * always the inverse of the RGB gamut to XYZ matrix.
- */
-Color bt709ToP3(Color e);
-Color bt709ToBt2100(Color e);
-Color p3ToBt709(Color e);
-Color p3ToBt2100(Color e);
-Color bt2100ToBt709(Color e);
-Color bt2100ToP3(Color e);
-
-/*
- * Identity conversion.
- */
-inline Color identityConversion(Color e) { return e; }
-
-/*
- * Get the conversion to apply to the HDR image for gain map generation
- */
-ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut);
-
-/*
- * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2.
- *
- * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is
- * treated as Bt.601 by DataSpace, hence we do the same.
- */
-Color yuv709To601(Color e_gamma);
-Color yuv709To2100(Color e_gamma);
-Color yuv601To709(Color e_gamma);
-Color yuv601To2100(Color e_gamma);
-Color yuv2100To709(Color e_gamma);
-Color yuv2100To601(Color e_gamma);
-
-/*
- * Performs a transformation at the chroma x and y coordinates provided on a YUV420 image.
- *
- * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets
- * this result, and UV gets the averaged result.
- *
- * x_chroma and y_chroma should be less than or equal to half the image's width and height
- * respecitively, since input is 4:2:0 subsampled.
- */
-void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
-                     ColorTransformFn fn);
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Gain map calculations
-
-/*
- * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR
- * luminances in linear space, and the hdr ratio to encode against.
- *
- * Note: since this library always uses gamma of 1.0, offsetSdr of 0.0, and
- * offsetHdr of 0.0, this function doesn't handle different metadata values for
- * these fields.
- */
-uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata);
-uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
-                   float log2MinContentBoost, float log2MaxContentBoost);
-
-/*
- * Calculates the linear luminance in nits after applying the given gain
- * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
- *
- * Note: similar to encodeGain(), this function only supports gamma 1.0,
- * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to
- * gainMapMax, as this library encodes.
- */
-Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata);
-Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost);
-Color applyGainLUT(Color e, float gain, GainLUT& gainLUT);
-
-/*
- * Helper for sampling from YUV 420 images.
- */
-Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
-
-/*
- * Helper for sampling from P010 images.
- *
- * Expect narrow-range image data for P010.
- */
-Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
-
-/*
- * Sample the image at the provided location, with a weighting based on nearby
- * pixels and the map scale factor.
- */
-Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-
-/*
- * Sample the image at the provided location, with a weighting based on nearby
- * pixels and the map scale factor.
- *
- * Expect narrow-range image data for P010.
- */
-Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-
-/*
- * Sample the gain value for the map from a given x,y coordinate on a scale
- * that is map scale factor larger than the map size.
- */
-float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y);
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
-                ShepardsIDW& weightTables);
-
-/*
- * Convert from Color to RGBA1010102.
- *
- * Alpha always set to 1.0.
- */
-uint32_t colorToRgba1010102(Color e_gamma);
-
-/*
- * Convert from Color to F16.
- *
- * Alpha always set to 1.0.
- */
-uint64_t colorToRgbaF16(Color e_gamma);
-
-} // namespace android::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
deleted file mode 100644
index 971b267..0000000
--- a/libs/ultrahdr/include/ultrahdr/icc.h
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_ICC_H
-#define ANDROID_ULTRAHDR_ICC_H
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-#include <utils/RefBase.h>
-#include <cmath>
-#include <string>
-
-#ifdef USE_BIG_ENDIAN
-#undef USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-namespace android::ultrahdr {
-
-typedef int32_t              Fixed;
-#define Fixed1               (1 << 16)
-#define MaxS32FitsInFloat    2147483520
-#define MinS32FitsInFloat    (-MaxS32FitsInFloat)
-#define FixedToFloat(x)      ((x) * 1.52587890625e-5f)
-
-typedef struct Matrix3x3 {
-    float vals[3][3];
-} Matrix3x3;
-
-// The D50 illuminant.
-constexpr float kD50_x = 0.9642f;
-constexpr float kD50_y = 1.0000f;
-constexpr float kD50_z = 0.8249f;
-
-enum {
-    // data_color_space
-    Signature_CMYK = 0x434D594B,
-    Signature_Gray = 0x47524159,
-    Signature_RGB  = 0x52474220,
-
-    // pcs
-    Signature_Lab  = 0x4C616220,
-    Signature_XYZ  = 0x58595A20,
-};
-
-typedef uint32_t FourByteTag;
-static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) {
-    return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d);
-}
-
-static constexpr char kICCIdentifier[] = "ICC_PROFILE";
-// 12 for the actual identifier, +2 for the chunk count and chunk index which
-// will always follow.
-static constexpr size_t kICCIdentifierSize = 14;
-
-// This is equal to the header size according to the ICC specification (128)
-// plus the size of the tag count (4).  We include the tag count since we
-// always require it to be present anyway.
-static constexpr size_t kICCHeaderSize = 132;
-
-// Contains a signature (4), offset (4), and size (4).
-static constexpr size_t kICCTagTableEntrySize = 12;
-
-// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12
-// bytes for a single XYZ number type (4 bytes per coordinate).
-static constexpr size_t kColorantTagSize = 20;
-
-static constexpr uint32_t kDisplay_Profile    = SetFourByteTag('m', 'n', 't', 'r');
-static constexpr uint32_t kRGB_ColorSpace     = SetFourByteTag('R', 'G', 'B', ' ');
-static constexpr uint32_t kXYZ_PCSSpace       = SetFourByteTag('X', 'Y', 'Z', ' ');
-static constexpr uint32_t kACSP_Signature     = SetFourByteTag('a', 'c', 's', 'p');
-
-static constexpr uint32_t kTAG_desc           = SetFourByteTag('d', 'e', 's', 'c');
-static constexpr uint32_t kTAG_TextType       = SetFourByteTag('m', 'l', 'u', 'c');
-static constexpr uint32_t kTAG_rXYZ           = SetFourByteTag('r', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_gXYZ           = SetFourByteTag('g', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_bXYZ           = SetFourByteTag('b', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_wtpt           = SetFourByteTag('w', 't', 'p', 't');
-static constexpr uint32_t kTAG_rTRC           = SetFourByteTag('r', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_gTRC           = SetFourByteTag('g', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_bTRC           = SetFourByteTag('b', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_cicp           = SetFourByteTag('c', 'i', 'c', 'p');
-static constexpr uint32_t kTAG_cprt           = SetFourByteTag('c', 'p', 'r', 't');
-static constexpr uint32_t kTAG_A2B0           = SetFourByteTag('A', '2', 'B', '0');
-static constexpr uint32_t kTAG_B2A0           = SetFourByteTag('B', '2', 'A', '0');
-
-static constexpr uint32_t kTAG_CurveType      = SetFourByteTag('c', 'u', 'r', 'v');
-static constexpr uint32_t kTAG_mABType        = SetFourByteTag('m', 'A', 'B', ' ');
-static constexpr uint32_t kTAG_mBAType        = SetFourByteTag('m', 'B', 'A', ' ');
-static constexpr uint32_t kTAG_ParaCurveType  = SetFourByteTag('p', 'a', 'r', 'a');
-
-
-static constexpr Matrix3x3 kSRGB = {{
-    // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync.
-    // 0.436065674f, 0.385147095f, 0.143066406f,
-    // 0.222488403f, 0.716873169f, 0.060607910f,
-    // 0.013916016f, 0.097076416f, 0.714096069f,
-    { FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0) },
-    { FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84) },
-    { FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF) },
-}};
-
-static constexpr Matrix3x3 kDisplayP3 = {{
-    {  0.515102f,   0.291965f,  0.157153f  },
-    {  0.241182f,   0.692236f,  0.0665819f },
-    { -0.00104941f, 0.0418818f, 0.784378f  },
-}};
-
-static constexpr Matrix3x3 kRec2020 = {{
-    {  0.673459f,   0.165661f,  0.125100f  },
-    {  0.279033f,   0.675338f,  0.0456288f },
-    { -0.00193139f, 0.0299794f, 0.797162f  },
-}};
-
-static constexpr uint32_t kCICPPrimariesSRGB = 1;
-static constexpr uint32_t kCICPPrimariesP3 = 12;
-static constexpr uint32_t kCICPPrimariesRec2020 = 9;
-
-static constexpr uint32_t kCICPTrfnSRGB = 1;
-static constexpr uint32_t kCICPTrfnLinear = 8;
-static constexpr uint32_t kCICPTrfnPQ = 16;
-static constexpr uint32_t kCICPTrfnHLG = 18;
-
-enum ParaCurveType {
-    kExponential_ParaCurveType = 0,
-    kGAB_ParaCurveType         = 1,
-    kGABC_ParaCurveType        = 2,
-    kGABDE_ParaCurveType       = 3,
-    kGABCDEF_ParaCurveType     = 4,
-};
-
-/**
- *  Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN.
- */
-static inline int float_saturate2int(float x) {
-    x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat;
-    x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat;
-    return (int)x;
-}
-
-static Fixed float_round_to_fixed(float x) {
-    return float_saturate2int((float)floor((double)x * Fixed1 + 0.5));
-}
-
-static uint16_t float_round_to_unorm16(float x) {
-    x = x * 65535.f + 0.5;
-    if (x > 65535) return 65535;
-    if (x < 0) return 0;
-    return static_cast<uint16_t>(x);
-}
-
-static void float_to_table16(const float f, uint8_t* table_16) {
-    *reinterpret_cast<uint16_t*>(table_16) = Endian_SwapBE16(float_round_to_unorm16(f));
-}
-
-static bool isfinitef_(float x) { return 0 == x*0; }
-
-struct ICCHeader {
-    // Size of the profile (computed)
-    uint32_t size;
-    // Preferred CMM type (ignored)
-    uint32_t cmm_type = 0;
-    // Version 4.3 or 4.4 if CICP is included.
-    uint32_t version = Endian_SwapBE32(0x04300000);
-    // Display device profile
-    uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile);
-    // RGB input color space;
-    uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace);
-    // Profile connection space.
-    uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace);
-    // Date and time (ignored)
-    uint8_t creation_date_time[12] = {0};
-    // Profile signature
-    uint32_t signature = Endian_SwapBE32(kACSP_Signature);
-    // Platform target (ignored)
-    uint32_t platform = 0;
-    // Flags: not embedded, can be used independently
-    uint32_t flags = 0x00000000;
-    // Device manufacturer (ignored)
-    uint32_t device_manufacturer = 0;
-    // Device model (ignored)
-    uint32_t device_model = 0;
-    // Device attributes (ignored)
-    uint8_t device_attributes[8] = {0};
-    // Relative colorimetric rendering intent
-    uint32_t rendering_intent = Endian_SwapBE32(1);
-    // D50 standard illuminant (X, Y, Z)
-    uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x));
-    uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y));
-    uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z));
-    // Profile creator (ignored)
-    uint32_t creator = 0;
-    // Profile id checksum (ignored)
-    uint8_t profile_id[16] = {0};
-    // Reserved (ignored)
-    uint8_t reserved[28] = {0};
-    // Technically not part of header, but required
-    uint32_t tag_count = 0;
-};
-
-class IccHelper {
-private:
-    static constexpr uint32_t kTrcTableSize = 65;
-    static constexpr uint32_t kGridSize = 17;
-    static constexpr size_t kNumChannels = 3;
-
-    static sp<DataStruct> write_text_tag(const char* text);
-    static std::string get_desc_string(const ultrahdr_transfer_function tf,
-                                       const ultrahdr_color_gamut gamut);
-    static sp<DataStruct> write_xyz_tag(float x, float y, float z);
-    static sp<DataStruct> write_trc_tag(const int table_entries, const void* table_16);
-    static sp<DataStruct> write_trc_tag(const TransferFunction& fn);
-    static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L);
-    static sp<DataStruct> write_cicp_tag(uint32_t color_primaries,
-                                         uint32_t transfer_characteristics);
-    static sp<DataStruct> write_mAB_or_mBA_tag(uint32_t type,
-                                               bool has_a_curves,
-                                               const uint8_t* grid_points,
-                                               const uint8_t* grid_16);
-    static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]);
-    static sp<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16);
-
-    // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input
-    // tag buffer assumed to be at least kColorantTagSize in size.
-    static bool tagsEqualToMatrix(const Matrix3x3& matrix,
-                                  const uint8_t* red_tag,
-                                  const uint8_t* green_tag,
-                                  const uint8_t* blue_tag);
-
-public:
-    // Output includes JPEG embedding identifier and chunk information, but not
-    // APPx information.
-    static sp<DataStruct> writeIccProfile(const ultrahdr_transfer_function tf,
-                                          const ultrahdr_color_gamut gamut);
-    // NOTE: this function is not robust; it can infer gamuts that IccHelper
-    // writes out but should not be considered a reference implementation for
-    // robust parsing of ICC profiles or their gamuts.
-    static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size);
-};
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_ICC_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
deleted file mode 100644
index b86ce5f4..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
-#define ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
-
-// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
-#include <cstdio>
-extern "C" {
-#include <jerror.h>
-#include <jpeglib.h>
-}
-#include <utils/Errors.h>
-#include <vector>
-
-// constraint on max width and max height is only due to device alloc constraints
-// Can tune these values basing on the target device
-static const int kMaxWidth = 8192;
-static const int kMaxHeight = 8192;
-
-namespace android::ultrahdr {
-/*
- * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
- * This class is not thread-safe.
- */
-class JpegDecoderHelper {
-public:
-    JpegDecoderHelper();
-    ~JpegDecoderHelper();
-    /*
-     * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
-     * calling this method, call getDecompressedImage() to get the image.
-     * Returns false if decompressing the image fails.
-     */
-    bool decompressImage(const void* image, int length, bool decodeToRGBA = false);
-    /*
-     * Returns the decompressed raw image buffer pointer. This method must be called only after
-     * calling decompressImage().
-     */
-    void* getDecompressedImagePtr();
-    /*
-     * Returns the decompressed raw image buffer size. This method must be called only after
-     * calling decompressImage().
-     */
-    size_t getDecompressedImageSize();
-    /*
-     * Returns the image width in pixels. This method must be called only after calling
-     * decompressImage().
-     */
-    size_t getDecompressedImageWidth();
-    /*
-     * Returns the image width in pixels. This method must be called only after calling
-     * decompressImage().
-     */
-    size_t getDecompressedImageHeight();
-    /*
-     * Returns the XMP data from the image.
-     */
-    void* getXMPPtr();
-    /*
-     * Returns the decompressed XMP buffer size. This method must be called only after
-     * calling decompressImage() or getCompressedImageParameters().
-     */
-    size_t getXMPSize();
-    /*
-     * Extracts EXIF package and updates the EXIF position / length without decoding the image.
-     */
-    bool extractEXIF(const void* image, int length);
-    /*
-     * Returns the EXIF data from the image.
-     * This method must be called after extractEXIF() or decompressImage().
-     */
-    void* getEXIFPtr();
-    /*
-     * Returns the decompressed EXIF buffer size. This method must be called only after
-     * calling decompressImage(), extractEXIF() or getCompressedImageParameters().
-     */
-    size_t getEXIFSize();
-    /*
-     * Returns the position offset of EXIF package
-     * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
-     * or -1  if no EXIF exists.
-     * This method must be called after extractEXIF() or decompressImage().
-     */
-    int getEXIFPos() { return mExifPos; }
-    /*
-     * Returns the ICC data from the image.
-     */
-    void* getICCPtr();
-    /*
-     * Returns the decompressed ICC buffer size. This method must be called only after
-     * calling decompressImage() or getCompressedImageParameters().
-     */
-    size_t getICCSize();
-    /*
-     * Decompresses metadata of the image. All vectors are owned by the caller.
-     */
-    bool getCompressedImageParameters(const void* image, int length, size_t* pWidth,
-                                      size_t* pHeight, std::vector<uint8_t>* iccData,
-                                      std::vector<uint8_t>* exifData);
-
-private:
-    bool decode(const void* image, int length, bool decodeToRGBA);
-    // Returns false if errors occur.
-    bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
-    bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    // Process 16 lines of Y and 16 lines of U/V each time.
-    // We must pass at least 16 scanlines according to libjpeg documentation.
-    static const int kCompressBatchSize = 16;
-    // The buffer that holds the decompressed result.
-    std::vector<JOCTET> mResultBuffer;
-    // The buffer that holds XMP Data.
-    std::vector<JOCTET> mXMPBuffer;
-    // The buffer that holds EXIF Data.
-    std::vector<JOCTET> mEXIFBuffer;
-    // The buffer that holds ICC Data.
-    std::vector<JOCTET> mICCBuffer;
-
-    // Resolution of the decompressed image.
-    size_t mWidth;
-    size_t mHeight;
-
-    // Position of EXIF package, default value is -1 which means no EXIF package appears.
-    ssize_t mExifPos = -1;
-};
-} /* namespace android::ultrahdr  */
-
-#endif // ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
deleted file mode 100644
index 9d06415..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
-#define ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
-
-// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
-#include <cstdio>
-#include <vector>
-
-extern "C" {
-#include <jerror.h>
-#include <jpeglib.h>
-}
-
-#include <utils/Errors.h>
-
-namespace android::ultrahdr {
-
-#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
-
-/*
- * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
- * This class is not thread-safe.
- */
-class JpegEncoderHelper {
-public:
-    JpegEncoderHelper();
-    ~JpegEncoderHelper();
-
-    /*
-     * Compresses YUV420Planer image to JPEG format. After calling this method, call
-     * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use.
-     * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of
-     * ICC segment which will be added to the compressed image.
-     * Returns false if errors occur during compression.
-     */
-    bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
-                       int lumaStride, int chromaStride, int quality, const void* iccBuffer,
-                       unsigned int iccSize);
-
-    /*
-     * Returns the compressed JPEG buffer pointer. This method must be called only after calling
-     * compressImage().
-     */
-    void* getCompressedImagePtr();
-
-    /*
-     * Returns the compressed JPEG buffer size. This method must be called only after calling
-     * compressImage().
-     */
-    size_t getCompressedImageSize();
-
-    /*
-     * Process 16 lines of Y and 16 lines of U/V each time.
-     * We must pass at least 16 scanlines according to libjpeg documentation.
-     */
-    static const int kCompressBatchSize = 16;
-
-private:
-    // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
-    // passed into jpeg library.
-    static void initDestination(j_compress_ptr cinfo);
-    static boolean emptyOutputBuffer(j_compress_ptr cinfo);
-    static void terminateDestination(j_compress_ptr cinfo);
-    static void outputErrorMessage(j_common_ptr cinfo);
-
-    // Returns false if errors occur.
-    bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
-                int lumaStride, int chromaStride, int quality, const void* iccBuffer,
-                unsigned int iccSize);
-    void setJpegDestination(jpeg_compress_struct* cinfo);
-    void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
-                               bool isSingleChannel);
-    // Returns false if errors occur.
-    bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer,
-                     int lumaStride, int chromaStride);
-    bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride);
-
-    // The block size for encoded jpeg image buffer.
-    static const int kBlockSize = 16384;
-
-    // The buffer that holds the compressed result.
-    std::vector<JOCTET> mResultBuffer;
-};
-
-} /* namespace android::ultrahdr  */
-
-#endif // ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
deleted file mode 100644
index 114c81d..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGR_H
-#define ANDROID_ULTRAHDR_JPEGR_H
-
-#include <cstdint>
-#include <vector>
-
-#include "ultrahdr/jpegdecoderhelper.h"
-#include "ultrahdr/jpegencoderhelper.h"
-#include "ultrahdr/jpegrerrorcode.h"
-#include "ultrahdr/ultrahdr.h"
-
-#ifndef FLT_MAX
-#define FLT_MAX 0x1.fffffep127f
-#endif
-
-namespace android::ultrahdr {
-
-// The current JPEGR version that we encode to
-static const char* const kJpegrVersion = "1.0";
-
-// Map is quarter res / sixteenth size
-static const size_t kMapDimensionScaleFactor = 4;
-
-// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
-// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
-// 1 sample is sufficient. We are using 2 here anyways
-static const int kMinWidth = 2 * kMapDimensionScaleFactor;
-static const int kMinHeight = 2 * kMapDimensionScaleFactor;
-
-// Minimum Codec Unit(MCU) for 420 sub-sampling is decided by JPEG encoder by parameter
-// JpegEncoderHelper::kCompressBatchSize.
-// The width and height of image under compression is expected to be a multiple of MCU size.
-// If this criteria is not satisfied, padding is done.
-static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
-
-/*
- * Holds information of jpegr image
- */
-struct jpegr_info_struct {
-    size_t width;
-    size_t height;
-    std::vector<uint8_t>* iccData;
-    std::vector<uint8_t>* exifData;
-};
-
-/*
- * Holds information for uncompressed image or gain map.
- */
-struct jpegr_uncompressed_struct {
-    // Pointer to the data location.
-    void* data;
-    // Width of the gain map or the luma plane of the image in pixels.
-    int width;
-    // Height of the gain map or the luma plane of the image in pixels.
-    int height;
-    // Color gamut.
-    ultrahdr_color_gamut colorGamut;
-
-    // Values below are optional
-    // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately
-    // after the luma plane.
-    void* chroma_data = nullptr;
-    // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If
-    // non-zero this value must be larger than or equal to luma width. If stride is
-    // uninitialized then it is assumed to be equal to luma width.
-    int luma_stride = 0;
-    // Stride of UV plane in number of pixels.
-    // 1. If this handle points to P010 image then this value must be larger than
-    //    or equal to luma width.
-    // 2. If this handle points to 420 image then this value must be larger than
-    //    or equal to (luma width / 2).
-    // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way,
-    // chroma_data is derived from luma ptr, chroma stride is derived from luma stride.
-    int chroma_stride = 0;
-};
-
-/*
- * Holds information for compressed image or gain map.
- */
-struct jpegr_compressed_struct {
-    // Pointer to the data location.
-    void* data;
-    // Used data length in bytes.
-    int length;
-    // Maximum available data length in bytes.
-    int maxLength;
-    // Color gamut.
-    ultrahdr_color_gamut colorGamut;
-};
-
-/*
- * Holds information for EXIF metadata.
- */
-struct jpegr_exif_struct {
-    // Pointer to the data location.
-    void* data;
-    // Data length;
-    int length;
-};
-
-typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
-typedef struct jpegr_compressed_struct* jr_compressed_ptr;
-typedef struct jpegr_exif_struct* jr_exif_ptr;
-typedef struct jpegr_info_struct* jr_info_ptr;
-
-class JpegR {
-public:
-    /*
-     * Experimental only
-     *
-     * Encode API-0
-     * Compress JPEGR image from 10-bit HDR YUV.
-     *
-     * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images,
-     * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed
-     * JPEG.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the destination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
-     *                the highest quality
-     * @param exif pointer to the exif metadata.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest, int quality, jr_exif_ptr exif);
-
-    /*
-     * Encode API-1
-     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
-     *
-     * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
-     * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same
-     * resolution. SDR input is assumed to use the sRGB transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
-     *                the highest quality
-     * @param exif pointer to the exif metadata.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
-                         ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality,
-                         jr_exif_ptr exif);
-
-    /*
-     * Encode API-2
-     * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG.
-     *
-     * This method requires HAL Hardware JPEG encoder.
-     *
-     * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the
-     * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and
-     * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB
-     * transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
-     *                            Note: the compressed SDR image must be the compressed
-     *                                  yuv420_image_ptr image in JPEG format.
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
-                         jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest);
-
-    /*
-     * Encode API-3
-     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
-     *
-     * This method requires HAL Hardware JPEG encoder.
-     *
-     * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input
-     * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an
-     * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same
-     * resolution. JPEG image is assumed to use the sRGB transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr,
-                         ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest);
-
-    /*
-     * Encode API-4
-     * Assemble JPEGR image from SDR JPEG and gainmap JPEG.
-     *
-     * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC
-     * profile if one isn't present in the input JPEG image.
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
-     * @param gainmapjpg_image_ptr gain map image compressed in jpeg format
-     * @param metadata metadata to be written in XMP of the primary jpeg
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
-                         jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
-                         jr_compressed_ptr dest);
-
-    /*
-     * Decode API
-     * Decompress JPEGR image.
-     *
-     * This method assumes that the JPEGR image contains an ICC profile with primaries that match
-     * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also
-     * assumes the base image uses the sRGB transfer function.
-     *
-     * This method only supports single gain map metadata values for fields that allow multi-channel
-     * metadata values.
-     * @param jpegr_image_ptr compressed JPEGR image.
-     * @param dest destination of the uncompressed JPEGR image.
-     * @param max_display_boost (optional) the maximum available boost supported by a display,
-     *                          the value must be greater than or equal to 1.0.
-     * @param exif destination of the decoded EXIF metadata. The default value is NULL where the
-                   decoder will do nothing about it. If configured not NULL the decoder will write
-                   EXIF data into this structure. The format is defined in {@code jpegr_exif_struct}
-     * @param output_format flag for setting output color format. Its value configures the output
-                            color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}.
-                            ----------------------------------------------------------------------
-                            |      output_format       |    decoded color format to be written   |
-                            ----------------------------------------------------------------------
-                            |     JPEGR_OUTPUT_SDR     |                RGBA_8888                |
-                            ----------------------------------------------------------------------
-                            | JPEGR_OUTPUT_HDR_LINEAR  |        (default)RGBA_F16 linear         |
-                            ----------------------------------------------------------------------
-                            |   JPEGR_OUTPUT_HDR_PQ    |             RGBA_1010102 PQ             |
-                            ----------------------------------------------------------------------
-                            |   JPEGR_OUTPUT_HDR_HLG   |            RGBA_1010102 HLG             |
-                            ----------------------------------------------------------------------
-     * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL
-                                where the decoder will do nothing about it. If configured not NULL
-                                the decoder will write the decoded gain_map data into this
-                                structure. The format is defined in
-                                {@code jpegr_uncompressed_struct}.
-     * @param metadata destination of the decoded metadata. The default value is NULL where the
-                       decoder will do nothing about it. If configured not NULL the decoder will
-                       write metadata into this structure. the format of metadata is defined in
-                       {@code ultrahdr_metadata_struct}.
-     * @return NO_ERROR if decoding succeeds, error code if error occurs.
-     */
-    status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
-                         float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr,
-                         ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR,
-                         jr_uncompressed_ptr gainmap_image_ptr = nullptr,
-                         ultrahdr_metadata_ptr metadata = nullptr);
-
-    /*
-     * Gets Info from JPEGR file without decoding it.
-     *
-     * This method only supports single gain map metadata values for fields that allow multi-channel
-     * metadata values.
-     *
-     * The output is filled jpegr_info structure
-     * @param jpegr_image_ptr compressed JPEGR image
-     * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info
-     *                            are owned by the caller
-     * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
-     */
-    status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr);
-
-protected:
-    /*
-     * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
-     * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images
-     * must be the same resolution. The SDR input is assumed to use the sRGB transfer function.
-     *
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param metadata everything but "version" is filled in this struct
-     * @param dest location at which gain map image is stored (caller responsible for memory
-                   of data).
-     * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                             jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
-                             ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest,
-                             bool sdr_is_601 = false);
-
-    /*
-     * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
-     * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as
-     * input, and calculate the 10-bit recovered image. The recovered output image is the same
-     * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format.
-     * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to
-     * be a decoded JPEG for the purpose of YUV interpration.
-     *
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param gainmap_image_ptr pointer to uncompressed gain map image struct.
-     * @param metadata JPEG/R metadata extracted from XMP.
-     * @param output_format flag for setting output color format. if set to
-     *                      {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image
-     *                      which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR.
-     * @param max_display_boost the maximum available boost supported by a display
-     * @param dest reconstructed HDR image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                          jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
-                          ultrahdr_output_format output_format, float max_display_boost,
-                          jr_uncompressed_ptr dest);
-
-private:
-    /*
-     * This method is called in the encoding pipeline. It will encode the gain map.
-     *
-     * @param gainmap_image_ptr pointer to uncompressed gain map image struct
-     * @param jpeg_enc_obj_ptr helper resource to compress gain map
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
-                             JpegEncoderHelper* jpeg_enc_obj_ptr);
-
-    /*
-     * This method is called to separate primary image and gain map image from JPEGR
-     *
-     * @param jpegr_image_ptr pointer to compressed JPEGR image.
-     * @param primary_jpg_image_ptr destination of primary image
-     * @param gainmap_jpg_image_ptr destination of compressed gain map image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
-                                           jr_compressed_ptr primary_jpg_image_ptr,
-                                           jr_compressed_ptr gainmap_jpg_image_ptr);
-
-    /*
-     * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
-     * the compressed gain map and optionally the exif package as inputs, and generate the XMP
-     * metadata, and finally append everything in the order of:
-     *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
-     *
-     * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
-     * conditions is fulfilled:
-     *  (1) EXIF package is available from outside input. I.e. pExif != nullptr.
-     *  (2) Input JPEG has EXIF.
-     * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
-     *
-     * @param primary_jpg_image_ptr destination of primary image
-     * @param gainmap_jpg_image_ptr destination of compressed gain map image
-     * @param (nullable) pExif EXIF package
-     * @param (nullable) pIcc ICC package
-     * @param icc_size length in bytes of ICC package
-     * @param metadata JPEG/R metadata to encode in XMP of the jpeg
-     * @param dest compressed JPEGR image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
-                           jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
-                           size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);
-
-    /*
-     * This method will tone map a HDR image to an SDR image.
-     *
-     * @param src pointer to uncompressed HDR image struct. HDR image is expected to be
-     *            in p010 color format
-     * @param dest pointer to store tonemapped SDR image
-     */
-    status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest);
-
-    /*
-     * This method will convert a YUV420 image from one YUV encoding to another in-place (eg.
-     * Bt.709 to Bt.601 YUV encoding).
-     *
-     * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that
-     * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data.
-     *
-     * @param image the YUV420 image to convert
-     * @param src_encoding input YUV encoding
-     * @param dest_encoding output YUV encoding
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
-                        ultrahdr_color_gamut dest_encoding);
-
-    /*
-     * This method will check the validity of the input arguments.
-     *
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
-     *                         be in 420p color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @return NO_ERROR if the input args are valid, error code is not valid.
-     */
-    status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                    jr_uncompressed_ptr yuv420_image_ptr,
-                                    ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr);
-
-    /*
-     * This method will check the validity of the input arguments.
-     *
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
-     *                         be in 420p color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the destination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
-     *                the highest quality
-     * @return NO_ERROR if the input args are valid, error code is not valid.
-     */
-    status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                    jr_uncompressed_ptr yuv420_image_ptr,
-                                    ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest,
-                                    int quality);
-};
-} // namespace android::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_JPEGR_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
deleted file mode 100644
index 5420e1c..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGRERRORCODE_H
-#define ANDROID_ULTRAHDR_JPEGRERRORCODE_H
-
-#include <utils/Errors.h>
-
-namespace android::ultrahdr {
-
-enum {
-    // status_t map for errors in the media framework
-    // OK or NO_ERROR or 0 represents no error.
-
-    // See system/core/include/utils/Errors.h
-    // System standard errors from -1 through (possibly) -133
-    //
-    // Errors with special meanings and side effects.
-    // INVALID_OPERATION:  Operation attempted in an illegal state (will try to signal to app).
-    // DEAD_OBJECT:        Signal from CodecBase to MediaCodec that MediaServer has died.
-    // NAME_NOT_FOUND:     Signal from CodecBase to MediaCodec that the component was not found.
-
-    // JPEGR errors
-    JPEGR_IO_ERROR_BASE                 = -10000,
-    ERROR_JPEGR_INVALID_INPUT_TYPE      = JPEGR_IO_ERROR_BASE,
-    ERROR_JPEGR_INVALID_OUTPUT_TYPE     = JPEGR_IO_ERROR_BASE - 1,
-    ERROR_JPEGR_INVALID_NULL_PTR        = JPEGR_IO_ERROR_BASE - 2,
-    ERROR_JPEGR_RESOLUTION_MISMATCH     = JPEGR_IO_ERROR_BASE - 3,
-    ERROR_JPEGR_BUFFER_TOO_SMALL        = JPEGR_IO_ERROR_BASE - 4,
-    ERROR_JPEGR_INVALID_COLORGAMUT      = JPEGR_IO_ERROR_BASE - 5,
-    ERROR_JPEGR_INVALID_TRANS_FUNC      = JPEGR_IO_ERROR_BASE - 6,
-    ERROR_JPEGR_INVALID_METADATA        = JPEGR_IO_ERROR_BASE - 7,
-    ERROR_JPEGR_UNSUPPORTED_METADATA    = JPEGR_IO_ERROR_BASE - 8,
-    ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND = JPEGR_IO_ERROR_BASE - 9,
-
-    JPEGR_RUNTIME_ERROR_BASE            = -20000,
-    ERROR_JPEGR_ENCODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 1,
-    ERROR_JPEGR_DECODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 2,
-    ERROR_JPEGR_CALCULATION_ERROR       = JPEGR_RUNTIME_ERROR_BASE - 3,
-    ERROR_JPEGR_METADATA_ERROR          = JPEGR_RUNTIME_ERROR_BASE - 4,
-    ERROR_JPEGR_TONEMAP_ERROR           = JPEGR_RUNTIME_ERROR_BASE - 5,
-
-    ERROR_JPEGR_UNSUPPORTED_FEATURE     = -20000,
-};
-
-}  // namespace android::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_JPEGRERRORCODE_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrutils.h b/libs/ultrahdr/include/ultrahdr/jpegrutils.h
deleted file mode 100644
index 4ab664e..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegrutils.h
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGRUTILS_H
-#define ANDROID_ULTRAHDR_JPEGRUTILS_H
-
-#include <ultrahdr/jpegr.h>
-#include <utils/RefBase.h>
-
-#include <sstream>
-#include <stdint.h>
-#include <string>
-#include <cstdio>
-
-namespace android::ultrahdr {
-
-static constexpr uint32_t EndianSwap32(uint32_t value) {
-    return ((value & 0xFF) << 24) |
-           ((value & 0xFF00) << 8) |
-           ((value & 0xFF0000) >> 8) |
-           (value >> 24);
-}
-static inline uint16_t EndianSwap16(uint16_t value) {
-    return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8));
-}
-
-#if USE_BIG_ENDIAN
-    #define Endian_SwapBE32(n) EndianSwap32(n)
-    #define Endian_SwapBE16(n) EndianSwap16(n)
-#else
-    #define Endian_SwapBE32(n) (n)
-    #define Endian_SwapBE16(n) (n)
-#endif
-
-struct ultrahdr_metadata_struct;
-/*
- * Mutable data structure. Holds information for metadata.
- */
-class DataStruct : public RefBase {
-private:
-    void* data;
-    int writePos;
-    int length;
-    ~DataStruct();
-
-public:
-    DataStruct(int s);
-    void* getData();
-    int getLength();
-    int getBytesWritten();
-    bool write8(uint8_t value);
-    bool write16(uint16_t value);
-    bool write32(uint32_t value);
-    bool write(const void* src, int size);
-};
-
-/*
- * Helper function used for writing data to destination.
- *
- * @param destination destination of the data to be written.
- * @param source source of data being written.
- * @param length length of the data to be written.
- * @param position cursor in desitination where the data is to be written.
- * @return status of succeed or error code.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position);
-
-
-/*
- * Parses XMP packet and fills metadata with data from XMP
- *
- * @param xmp_data pointer to XMP packet
- * @param xmp_size size of XMP packet
- * @param metadata place to store HDR metadata values
- * @return true if metadata is successfully retrieved, false otherwise
-*/
-bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata);
-
-/*
- * This method generates XMP metadata for the primary image.
- *
- * below is an example of the XMP metadata that this function generates where
- * secondary_image_length = 1000
- *
- * <x:xmpmeta
- *   xmlns:x="adobe:ns:meta/"
- *   x:xmptk="Adobe XMP Core 5.1.2">
- *   <rdf:RDF
- *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
- *     <rdf:Description
- *       xmlns:Container="http://ns.google.com/photos/1.0/container/"
- *       xmlns:Item="http://ns.google.com/photos/1.0/container/item/"
- *       xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
- *       hdrgm:Version="1">
- *       <Container:Directory>
- *         <rdf:Seq>
- *           <rdf:li
- *             rdf:parseType="Resource">
- *             <Container:Item
- *               Item:Semantic="Primary"
- *               Item:Mime="image/jpeg"/>
- *           </rdf:li>
- *           <rdf:li
- *             rdf:parseType="Resource">
- *             <Container:Item
- *               Item:Semantic="GainMap"
- *               Item:Mime="image/jpeg"
- *               Item:Length="1000"/>
- *           </rdf:li>
- *         </rdf:Seq>
- *       </Container:Directory>
- *     </rdf:Description>
- *   </rdf:RDF>
- * </x:xmpmeta>
- *
- * @param secondary_image_length length of secondary image
- * @return XMP metadata in type of string
- */
-std::string generateXmpForPrimaryImage(int secondary_image_length,
-                                       ultrahdr_metadata_struct& metadata);
-
-/*
- * This method generates XMP metadata for the recovery map image.
- *
- * below is an example of the XMP metadata that this function generates where
- * max_content_boost = 8.0
- * min_content_boost = 0.5
- *
- * <x:xmpmeta
- *   xmlns:x="adobe:ns:meta/"
- *   x:xmptk="Adobe XMP Core 5.1.2">
- *   <rdf:RDF
- *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
- *     <rdf:Description
- *       xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
- *       hdrgm:Version="1"
- *       hdrgm:GainMapMin="-1"
- *       hdrgm:GainMapMax="3"
- *       hdrgm:Gamma="1"
- *       hdrgm:OffsetSDR="0"
- *       hdrgm:OffsetHDR="0"
- *       hdrgm:HDRCapacityMin="0"
- *       hdrgm:HDRCapacityMax="3"
- *       hdrgm:BaseRenditionIsHDR="False"/>
- *   </rdf:RDF>
- * </x:xmpmeta>
- *
- * @param metadata JPEG/R metadata to encode as XMP
- * @return XMP metadata in type of string
- */
- std::string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata);
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_JPEGRUTILS_H
diff --git a/libs/ultrahdr/include/ultrahdr/multipictureformat.h b/libs/ultrahdr/include/ultrahdr/multipictureformat.h
deleted file mode 100644
index c5bd09d..0000000
--- a/libs/ultrahdr/include/ultrahdr/multipictureformat.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
-#define ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
-
-#include <ultrahdr/jpegrutils.h>
-
-#ifdef USE_BIG_ENDIAN
-#undef USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-namespace android::ultrahdr {
-
-constexpr size_t kNumPictures = 2;
-constexpr size_t kMpEndianSize = 4;
-constexpr uint16_t kTagSerializedCount = 3;
-constexpr uint32_t kTagSize = 12;
-
-constexpr uint16_t kTypeLong = 0x4;
-constexpr uint16_t kTypeUndefined = 0x7;
-
-static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'};
-constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00};
-constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A};
-
-constexpr uint16_t kVersionTag = 0xB000;
-constexpr uint16_t kVersionType = kTypeUndefined;
-constexpr uint32_t kVersionCount = 4;
-constexpr size_t kVersionSize = 4;
-constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'};
-
-constexpr uint16_t kNumberOfImagesTag = 0xB001;
-constexpr uint16_t kNumberOfImagesType = kTypeLong;
-constexpr uint32_t kNumberOfImagesCount = 1;
-
-constexpr uint16_t kMPEntryTag = 0xB002;
-constexpr uint16_t kMPEntryType = kTypeUndefined;
-constexpr uint32_t kMPEntrySize = 16;
-
-constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000;
-constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000;
-
-size_t calculateMpfSize();
-sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
-                           int secondary_image_size, int secondary_image_offset);
-
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
deleted file mode 100644
index 0252391..0000000
--- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_ULTRAHDR_H
-#define ANDROID_ULTRAHDR_ULTRAHDR_H
-
-#include <string>
-
-namespace android::ultrahdr {
-// Color gamuts for image data
-typedef enum {
-  ULTRAHDR_COLORGAMUT_UNSPECIFIED = -1,
-  ULTRAHDR_COLORGAMUT_BT709,
-  ULTRAHDR_COLORGAMUT_P3,
-  ULTRAHDR_COLORGAMUT_BT2100,
-  ULTRAHDR_COLORGAMUT_MAX = ULTRAHDR_COLORGAMUT_BT2100,
-} ultrahdr_color_gamut;
-
-// Transfer functions for image data
-// TODO: TF LINEAR is deprecated, remove this enum and the code surrounding it.
-typedef enum {
-  ULTRAHDR_TF_UNSPECIFIED = -1,
-  ULTRAHDR_TF_LINEAR = 0,
-  ULTRAHDR_TF_HLG = 1,
-  ULTRAHDR_TF_PQ = 2,
-  ULTRAHDR_TF_SRGB = 3,
-  ULTRAHDR_TF_MAX = ULTRAHDR_TF_SRGB,
-} ultrahdr_transfer_function;
-
-// Target output formats for decoder
-typedef enum {
-  ULTRAHDR_OUTPUT_UNSPECIFIED = -1,
-  ULTRAHDR_OUTPUT_SDR,          // SDR in RGBA_8888 color format
-  ULTRAHDR_OUTPUT_HDR_LINEAR,   // HDR in F16 color format (linear)
-  ULTRAHDR_OUTPUT_HDR_PQ,       // HDR in RGBA_1010102 color format (PQ transfer function)
-  ULTRAHDR_OUTPUT_HDR_HLG,      // HDR in RGBA_1010102 color format (HLG transfer function)
-  ULTRAHDR_OUTPUT_MAX = ULTRAHDR_OUTPUT_HDR_HLG,
-} ultrahdr_output_format;
-
-/*
- * Holds information for gain map related metadata.
- *
- * Not: all values stored in linear. This differs from the metadata encoding in XMP, where
- * maxContentBoost (aka gainMapMax), minContentBoost (aka gainMapMin), hdrCapacityMin, and
- * hdrCapacityMax are stored in log2 space.
- */
-struct ultrahdr_metadata_struct {
-  // Ultra HDR format version
-  std::string version;
-  // Max Content Boost for the map
-  float maxContentBoost;
-  // Min Content Boost for the map
-  float minContentBoost;
-  // Gamma of the map data
-  float gamma;
-  // Offset for SDR data in map calculations
-  float offsetSdr;
-  // Offset for HDR data in map calculations
-  float offsetHdr;
-  // HDR capacity to apply the map at all
-  float hdrCapacityMin;
-  // HDR capacity to apply the map completely
-  float hdrCapacityMax;
-};
-typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr;
-
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_ULTRAHDR_H
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
deleted file mode 100644
index 2e7940c..0000000
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <ultrahdr/jpegdecoderhelper.h>
-
-#include <utils/Log.h>
-
-#include <errno.h>
-#include <setjmp.h>
-#include <string>
-
-using namespace std;
-
-namespace android::ultrahdr {
-
-#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
-
-const uint32_t kAPP0Marker = JPEG_APP0;     // JFIF
-const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
-const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
-
-constexpr uint32_t kICCMarkerHeaderSize = 14;
-constexpr uint8_t kICCSig[] = {
-        'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
-};
-constexpr uint8_t kXmpNameSpace[] = {
-        'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
-        '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
-};
-constexpr uint8_t kExifIdCode[] = {
-        'E', 'x', 'i', 'f', '\0', '\0',
-};
-
-struct jpegr_source_mgr : jpeg_source_mgr {
-    jpegr_source_mgr(const uint8_t* ptr, int len);
-    ~jpegr_source_mgr();
-
-    const uint8_t* mBufferPtr;
-    size_t mBufferLength;
-};
-
-struct jpegrerror_mgr {
-    struct jpeg_error_mgr pub;
-    jmp_buf setjmp_buffer;
-};
-
-static void jpegr_init_source(j_decompress_ptr cinfo) {
-    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
-    src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr);
-    src->bytes_in_buffer = src->mBufferLength;
-}
-
-static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) {
-    ALOGE("%s : should not get here", __func__);
-    return FALSE;
-}
-
-static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
-    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
-
-    if (num_bytes > static_cast<long>(src->bytes_in_buffer)) {
-        ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer");
-    } else {
-        src->next_input_byte += num_bytes;
-        src->bytes_in_buffer -= num_bytes;
-    }
-}
-
-static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
-
-jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len)
-      : mBufferPtr(ptr), mBufferLength(len) {
-    init_source = jpegr_init_source;
-    fill_input_buffer = jpegr_fill_input_buffer;
-    skip_input_data = jpegr_skip_input_data;
-    resync_to_restart = jpeg_resync_to_restart;
-    term_source = jpegr_term_source;
-}
-
-jpegr_source_mgr::~jpegr_source_mgr() {}
-
-static void jpegrerror_exit(j_common_ptr cinfo) {
-    jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err);
-    longjmp(err->setjmp_buffer, 1);
-}
-
-JpegDecoderHelper::JpegDecoderHelper() {}
-
-JpegDecoderHelper::~JpegDecoderHelper() {}
-
-bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) {
-    if (image == nullptr || length <= 0) {
-        ALOGE("Image size can not be handled: %d", length);
-        return false;
-    }
-    mResultBuffer.clear();
-    mXMPBuffer.clear();
-    return decode(image, length, decodeToRGBA);
-}
-
-void* JpegDecoderHelper::getDecompressedImagePtr() {
-    return mResultBuffer.data();
-}
-
-size_t JpegDecoderHelper::getDecompressedImageSize() {
-    return mResultBuffer.size();
-}
-
-void* JpegDecoderHelper::getXMPPtr() {
-    return mXMPBuffer.data();
-}
-
-size_t JpegDecoderHelper::getXMPSize() {
-    return mXMPBuffer.size();
-}
-
-void* JpegDecoderHelper::getEXIFPtr() {
-    return mEXIFBuffer.data();
-}
-
-size_t JpegDecoderHelper::getEXIFSize() {
-    return mEXIFBuffer.size();
-}
-
-void* JpegDecoderHelper::getICCPtr() {
-    return mICCBuffer.data();
-}
-
-size_t JpegDecoderHelper::getICCSize() {
-    return mICCBuffer.size();
-}
-
-size_t JpegDecoderHelper::getDecompressedImageWidth() {
-    return mWidth;
-}
-
-size_t JpegDecoderHelper::getDecompressedImageHeight() {
-    return mHeight;
-}
-
-// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
-// in the image file.
-// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
-// two bytes of package length which is stored in marker->original_length, and the real data
-// which is stored in marker->data.
-bool JpegDecoderHelper::extractEXIF(const void* image, int length) {
-    jpeg_decompress_struct cinfo;
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
-    jpegrerror_mgr myerr;
-
-    cinfo.err = jpeg_std_error(&myerr.pub);
-    myerr.pub.error_exit = jpegrerror_exit;
-
-    if (setjmp(myerr.setjmp_buffer)) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-    jpeg_create_decompress(&cinfo);
-
-    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
-
-    cinfo.src = &mgr;
-    jpeg_read_header(&cinfo, TRUE);
-
-    size_t pos = 2;  // position after SOI
-    for (jpeg_marker_struct* marker = cinfo.marker_list;
-         marker;
-         marker = marker->next) {
-
-        pos += 4;
-        pos += marker->original_length;
-
-        if (marker->marker != kAPP1Marker) {
-            continue;
-        }
-
-        const unsigned int len = marker->data_length;
-
-        if (len > sizeof(kExifIdCode) &&
-            !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-            mEXIFBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
-            mExifPos = pos - marker->original_length;
-            break;
-        }
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-    return true;
-}
-
-bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
-    bool status = true;
-    jpeg_decompress_struct cinfo;
-    jpegrerror_mgr myerr;
-    cinfo.err = jpeg_std_error(&myerr.pub);
-    myerr.pub.error_exit = jpegrerror_exit;
-    if (setjmp(myerr.setjmp_buffer)) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-
-    jpeg_create_decompress(&cinfo);
-
-    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
-
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
-    cinfo.src = &mgr;
-    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-
-    // Save XMP data, EXIF data, and ICC data.
-    // Here we only handle the first XMP / EXIF / ICC package.
-    // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
-    // two bytes of package length which is stored in marker->original_length, and the real data
-    // which is stored in marker->data.
-    bool exifAppears = false;
-    bool xmpAppears = false;
-    bool iccAppears = false;
-    size_t pos = 2;  // position after SOI
-    for (jpeg_marker_struct* marker = cinfo.marker_list;
-         marker && !(exifAppears && xmpAppears && iccAppears);
-         marker = marker->next) {
-         pos += 4;
-         pos += marker->original_length;
-        if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
-            continue;
-        }
-        const unsigned int len = marker->data_length;
-        if (!xmpAppears &&
-            len > sizeof(kXmpNameSpace) &&
-            !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
-            mXMPBuffer.resize(len+1, 0);
-            memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
-            xmpAppears = true;
-        } else if (!exifAppears &&
-                   len > sizeof(kExifIdCode) &&
-                   !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-            mEXIFBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
-            exifAppears = true;
-            mExifPos = pos - marker->original_length;
-        } else if (!iccAppears &&
-                   len > sizeof(kICCSig) &&
-                   !memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
-            mICCBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len);
-            iccAppears = true;
-        }
-    }
-
-    mWidth = cinfo.image_width;
-    mHeight = cinfo.image_height;
-    if (mWidth > kMaxWidth || mHeight > kMaxHeight) {
-        status = false;
-        goto CleanUp;
-    }
-
-    if (decodeToRGBA) {
-        // The primary image is expected to be yuv420 sampling
-        if (cinfo.jpeg_color_space != JCS_YCbCr) {
-            status = false;
-            ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__);
-            goto CleanUp;
-        }
-        if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
-            cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
-            cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
-            status = false;
-            ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
-            goto CleanUp;
-        }
-        // 4 bytes per pixel
-        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
-        cinfo.out_color_space = JCS_EXT_RGBA;
-    } else {
-        if (cinfo.jpeg_color_space == JCS_YCbCr) {
-            if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
-                cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
-                cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
-                status = false;
-                ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__);
-                goto CleanUp;
-            }
-            mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
-        } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
-            mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
-        } else {
-            status = false;
-            ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__);
-            goto CleanUp;
-        }
-        cinfo.out_color_space = cinfo.jpeg_color_space;
-        cinfo.raw_data_out = TRUE;
-    }
-
-    cinfo.dct_method = JDCT_ISLOW;
-    jpeg_start_decompress(&cinfo);
-    if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
-                    cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
-        status = false;
-        goto CleanUp;
-    }
-
-CleanUp:
-    jpeg_finish_decompress(&cinfo);
-    jpeg_destroy_decompress(&cinfo);
-
-    return status;
-}
-
-bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
-                                   bool isSingleChannel) {
-    return isSingleChannel
-            ? decompressSingleChannel(cinfo, dest)
-            : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest)
-                                                        : decompressYUV(cinfo, dest));
-}
-
-bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth,
-                                                     size_t* pHeight, std::vector<uint8_t>* iccData,
-                                                     std::vector<uint8_t>* exifData) {
-    jpeg_decompress_struct cinfo;
-    jpegrerror_mgr myerr;
-    cinfo.err = jpeg_std_error(&myerr.pub);
-    myerr.pub.error_exit = jpegrerror_exit;
-    if (setjmp(myerr.setjmp_buffer)) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-    jpeg_create_decompress(&cinfo);
-
-    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
-
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
-    cinfo.src = &mgr;
-    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-
-    if (pWidth != nullptr) {
-        *pWidth = cinfo.image_width;
-    }
-    if (pHeight != nullptr) {
-        *pHeight = cinfo.image_height;
-    }
-
-    if (iccData != nullptr) {
-        for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
-            if (marker->marker != kAPP2Marker) {
-                continue;
-            }
-            if (marker->data_length <= kICCMarkerHeaderSize ||
-                memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) {
-                continue;
-            }
-
-            iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length);
-        }
-    }
-
-    if (exifData != nullptr) {
-        bool exifAppears = false;
-        for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears;
-             marker = marker->next) {
-            if (marker->marker != kAPP1Marker) {
-                continue;
-            }
-
-            const unsigned int len = marker->data_length;
-            if (len >= sizeof(kExifIdCode) &&
-                !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-                exifData->resize(len, 0);
-                memcpy(static_cast<void*>(exifData->data()), marker->data, len);
-                exifAppears = true;
-            }
-        }
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-    return true;
-}
-
-bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-    JSAMPLE* out = (JSAMPLE*)dest;
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false;
-        out += cinfo->image_width * 4;
-    }
-    return true;
-}
-
-bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPROW cb[kCompressBatchSize / 2];
-    JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3]{y, cb, cr};
-
-    size_t y_plane_size = cinfo->image_width * cinfo->image_height;
-    size_t uv_plane_size = y_plane_size / 4;
-    uint8_t* y_plane = const_cast<uint8_t*>(dest);
-    uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size);
-    uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    bool is_width_aligned = (aligned_width == cinfo->image_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    uint8_t* v_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPROW cb_intrm[kCompressBatchSize / 2];
-    JSAMPROW cr_intrm[kCompressBatchSize / 2];
-    JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
-    if (!is_width_aligned) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
-        v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-        }
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            int offset_intrm = i * (aligned_width / 2);
-            cb_intrm[i] = u_plane_intrm + offset_intrm;
-            cr_intrm[i] = v_plane_intrm + offset_intrm;
-        }
-    }
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->output_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * cinfo->image_width;
-            } else {
-                y[i] = empty.get();
-            }
-        }
-        // cb, cr only have half scanlines
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            size_t scanline = cinfo->output_scanline / 2 + i;
-            if (scanline < cinfo->image_height / 2) {
-                int offset = scanline * (cinfo->image_width / 2);
-                cb[i] = u_plane + offset;
-                cr[i] = v_plane + offset;
-            } else {
-                cb[i] = cr[i] = empty.get();
-            }
-        }
-
-        int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
-                                           kCompressBatchSize);
-        if (processed != kCompressBatchSize) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-        if (!is_width_aligned) {
-            for (int i = 0; i < kCompressBatchSize; ++i) {
-                memcpy(y[i], y_intrm[i], cinfo->image_width);
-            }
-            for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-                memcpy(cb[i], cb_intrm[i], cinfo->image_width / 2);
-                memcpy(cr[i], cr_intrm[i], cinfo->image_width / 2);
-            }
-        }
-    }
-    return true;
-}
-
-bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo,
-                                                const uint8_t* dest) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1]{y};
-
-    uint8_t* y_plane = const_cast<uint8_t*>(dest);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    bool is_width_aligned = (aligned_width == cinfo->image_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPARRAY planes_intrm[1]{y_intrm};
-    if (!is_width_aligned) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-        }
-    }
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->output_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * cinfo->image_width;
-            } else {
-                y[i] = empty.get();
-            }
-        }
-
-        int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
-                                           kCompressBatchSize);
-        if (processed != kCompressBatchSize / 2) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-        if (!is_width_aligned) {
-            for (int i = 0; i < kCompressBatchSize; ++i) {
-                memcpy(y[i], y_intrm[i], cinfo->image_width);
-            }
-        }
-    }
-    return true;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp
deleted file mode 100644
index 13ae742..0000000
--- a/libs/ultrahdr/jpegencoderhelper.cpp
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstring>
-#include <memory>
-#include <vector>
-
-#include <ultrahdr/jpegencoderhelper.h>
-#include <utils/Log.h>
-
-namespace android::ultrahdr {
-
-// The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
-struct destination_mgr {
-    struct jpeg_destination_mgr mgr;
-    JpegEncoderHelper* encoder;
-};
-
-JpegEncoderHelper::JpegEncoderHelper() {}
-
-JpegEncoderHelper::~JpegEncoderHelper() {}
-
-bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
-                                      int height, int lumaStride, int chromaStride, int quality,
-                                      const void* iccBuffer, unsigned int iccSize) {
-    mResultBuffer.clear();
-    if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer,
-                iccSize)) {
-        return false;
-    }
-    ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height,
-          mResultBuffer.size());
-    return true;
-}
-
-void* JpegEncoderHelper::getCompressedImagePtr() {
-    return mResultBuffer.data();
-}
-
-size_t JpegEncoderHelper::getCompressedImageSize() {
-    return mResultBuffer.size();
-}
-
-void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    buffer.resize(kBlockSize);
-    dest->mgr.next_output_byte = &buffer[0];
-    dest->mgr.free_in_buffer = buffer.size();
-}
-
-boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    size_t oldsize = buffer.size();
-    buffer.resize(oldsize + kBlockSize);
-    dest->mgr.next_output_byte = &buffer[oldsize];
-    dest->mgr.free_in_buffer = kBlockSize;
-    return true;
-}
-
-void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
-}
-
-void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
-    char buffer[JMSG_LENGTH_MAX];
-
-    /* Create the message */
-    (*cinfo->err->format_message)(cinfo, buffer);
-    ALOGE("%s\n", buffer);
-}
-
-bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
-                               int height, int lumaStride, int chromaStride, int quality,
-                               const void* iccBuffer, unsigned int iccSize) {
-    jpeg_compress_struct cinfo;
-    jpeg_error_mgr jerr;
-
-    cinfo.err = jpeg_std_error(&jerr);
-    cinfo.err->output_message = &outputErrorMessage;
-    jpeg_create_compress(&cinfo);
-    setJpegDestination(&cinfo);
-    setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr);
-    jpeg_start_compress(&cinfo, TRUE);
-    if (iccBuffer != nullptr && iccSize > 0) {
-        jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
-    }
-    bool status = cinfo.num_components == 1
-            ? compressY(&cinfo, yBuffer, lumaStride)
-            : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride);
-    jpeg_finish_compress(&cinfo);
-    jpeg_destroy_compress(&cinfo);
-
-    return status;
-}
-
-void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
-    destination_mgr* dest = static_cast<struct destination_mgr*>(
-            (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT,
-                                       sizeof(destination_mgr)));
-    dest->encoder = this;
-    dest->mgr.init_destination = &initDestination;
-    dest->mgr.empty_output_buffer = &emptyOutputBuffer;
-    dest->mgr.term_destination = &terminateDestination;
-    cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
-}
-
-void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
-                                              jpeg_compress_struct* cinfo, bool isSingleChannel) {
-    cinfo->image_width = width;
-    cinfo->image_height = height;
-    cinfo->input_components = isSingleChannel ? 1 : 3;
-    cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr;
-    jpeg_set_defaults(cinfo);
-    jpeg_set_quality(cinfo, quality, TRUE);
-    cinfo->raw_data_in = TRUE;
-    cinfo->dct_method = JDCT_ISLOW;
-    cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
-    cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
-    for (int i = 1; i < cinfo->num_components; i++) {
-        cinfo->comp_info[i].h_samp_factor = 1;
-        cinfo->comp_info[i].v_samp_factor = 1;
-    }
-}
-
-bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
-                                    const uint8_t* uvBuffer, int lumaStride, int chromaStride) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPROW cb[kCompressBatchSize / 2];
-    JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3]{y, cb, cr};
-
-    size_t y_plane_size = lumaStride * cinfo->image_height;
-    size_t u_plane_size = chromaStride * cinfo->image_height / 2;
-    uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
-    uint8_t* u_plane = const_cast<uint8_t*>(uvBuffer);
-    uint8_t* v_plane = const_cast<uint8_t*>(u_plane + u_plane_size);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    const bool need_padding = (lumaStride < aligned_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    uint8_t* v_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPROW cb_intrm[kCompressBatchSize / 2];
-    JSAMPROW cr_intrm[kCompressBatchSize / 2];
-    JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
-    if (need_padding) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
-        v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-            memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
-        }
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            int offset_intrm = i * (aligned_width / 2);
-            cb_intrm[i] = u_plane_intrm + offset_intrm;
-            cr_intrm[i] = v_plane_intrm + offset_intrm;
-            memset(cb_intrm[i] + cinfo->image_width / 2, 0,
-                   (aligned_width - cinfo->image_width) / 2);
-            memset(cr_intrm[i] + cinfo->image_width / 2, 0,
-                   (aligned_width - cinfo->image_width) / 2);
-        }
-    }
-
-    while (cinfo->next_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->next_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * lumaStride;
-            } else {
-                y[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(y_intrm[i], y[i], cinfo->image_width);
-            }
-        }
-        // cb, cr only have half scanlines
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            size_t scanline = cinfo->next_scanline / 2 + i;
-            if (scanline < cinfo->image_height / 2) {
-                int offset = scanline * chromaStride;
-                cb[i] = u_plane + offset;
-                cr[i] = v_plane + offset;
-            } else {
-                cb[i] = cr[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2);
-                memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2);
-            }
-        }
-        int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
-                                            kCompressBatchSize);
-        if (processed != kCompressBatchSize) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-    }
-    return true;
-}
-
-bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
-                                  int lumaStride) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1]{y};
-
-    uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    const bool need_padding = (lumaStride < aligned_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPARRAY planes_intrm[]{y_intrm};
-    if (need_padding) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-            memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
-        }
-    }
-
-    while (cinfo->next_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->next_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * lumaStride;
-            } else {
-                y[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(y_intrm[i], y[i], cinfo->image_width);
-            }
-        }
-        int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
-                                            kCompressBatchSize);
-        if (processed != kCompressBatchSize / 2) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-    }
-    return true;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
deleted file mode 100644
index 3d70fce..0000000
--- a/libs/ultrahdr/jpegr.cpp
+++ /dev/null
@@ -1,1503 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cmath>
-#include <condition_variable>
-#include <deque>
-#include <memory>
-#include <mutex>
-#include <thread>
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-#include <ultrahdr/multipictureformat.h>
-
-#include <image_io/base/data_segment_data_source.h>
-#include <image_io/jpeg/jpeg_info.h>
-#include <image_io/jpeg/jpeg_info_builder.h>
-#include <image_io/jpeg/jpeg_marker.h>
-#include <image_io/jpeg/jpeg_scanner.h>
-
-#include <utils/Log.h>
-
-using namespace std;
-using namespace photos_editing_formats::image_io;
-
-namespace android::ultrahdr {
-
-#define USE_SRGB_INVOETF_LUT 1
-#define USE_HLG_OETF_LUT 1
-#define USE_PQ_OETF_LUT 1
-#define USE_HLG_INVOETF_LUT 1
-#define USE_PQ_INVOETF_LUT 1
-#define USE_APPLY_GAIN_LUT 1
-
-#define JPEGR_CHECK(x)          \
-  {                             \
-    status_t status = (x);      \
-    if ((status) != NO_ERROR) { \
-      return status;            \
-    }                           \
-  }
-
-// JPEG compress quality (0 ~ 100) for gain map
-static const int kMapCompressQuality = 85;
-
-#define CONFIG_MULTITHREAD 1
-int GetCPUCoreCount() {
-  int cpuCoreCount = 1;
-#if CONFIG_MULTITHREAD
-#if defined(_SC_NPROCESSORS_ONLN)
-  cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
-#else
-  // _SC_NPROC_ONLN must be defined...
-  cpuCoreCount = sysconf(_SC_NPROC_ONLN);
-#endif
-#endif
-  return cpuCoreCount;
-}
-
-/*
- * Helper function copies the JPEG image from without EXIF.
- *
- * @param pDest destination of the data to be written.
- * @param pSource source of data being written.
- * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
- *                 (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
- * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
- */
-static void copyJpegWithoutExif(jr_compressed_ptr pDest,
-                                jr_compressed_ptr pSource,
-                                size_t exif_pos,
-                                size_t exif_size) {
-  const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
-  pDest->length = pSource->length - exif_size - exif_offset;
-  pDest->data = new uint8_t[pDest->length];
-  pDest->maxLength = pDest->length;
-  pDest->colorGamut = pSource->colorGamut;
-  memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
-  memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
-         (uint8_t*)pSource->data + exif_pos + exif_size,
-         pSource->length - exif_pos - exif_size);
-}
-
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                       jr_uncompressed_ptr yuv420_image_ptr,
-                                       ultrahdr_transfer_function hdr_tf,
-                                       jr_compressed_ptr dest_ptr) {
-  if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
-    ALOGE("Received nullptr for input p010 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
-    ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
-          p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
-    ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
-          kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
-    ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
-          kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
-    ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
-    ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
-          p010_image_ptr->luma_stride, p010_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->chroma_data != nullptr &&
-      p010_image_ptr->chroma_stride < p010_image_ptr->width) {
-    ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
-          p010_image_ptr->chroma_stride, p010_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
-    ALOGE("Received nullptr for destination");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
-    ALOGE("Invalid hdr transfer function %d", hdr_tf);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (yuv420_image_ptr == nullptr) {
-    return NO_ERROR;
-  }
-  if (yuv420_image_ptr->data == nullptr) {
-    ALOGE("Received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420_image_ptr->luma_stride != 0 &&
-      yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
-    ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
-          yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (yuv420_image_ptr->chroma_data != nullptr &&
-      yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
-    ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
-          yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width != yuv420_image_ptr->width ||
-      p010_image_ptr->height != yuv420_image_ptr->height) {
-    ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
-          p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-  if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
-    ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  return NO_ERROR;
-}
-
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                       jr_uncompressed_ptr yuv420_image_ptr,
-                                       ultrahdr_transfer_function hdr_tf,
-                                       jr_compressed_ptr dest_ptr, int quality) {
-  if (quality < 0 || quality > 100) {
-    ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
-}
-
-/* Encode API-0 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
-  // validate input arguments
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
-      ret != NO_ERROR) {
-    return ret;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr for exif metadata");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-
-  const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
-  unique_ptr<uint8_t[]> yuv420_image_data =
-          make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
-  jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
-                                            .width = p010_image.width,
-                                            .height = p010_image.height,
-                                            .colorGamut = p010_image.colorGamut,
-                                            .luma_stride = yu420_luma_stride,
-                                            .chroma_data = nullptr,
-                                            .chroma_stride = yu420_luma_stride >> 1};
-  uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-  yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-
-  // tone map
-  JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                            .length = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .maxLength = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
-
-  // convert to Bt601 YUV encoding for JPEG encode
-  if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
-    JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
-  }
-
-  // compress 420 image
-  JpegEncoderHelper jpeg_enc_obj_yuv420;
-  if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
-                                         reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
-                                         yuv420_image.width, yuv420_image.height,
-                                         yuv420_image.luma_stride, yuv420_image.chroma_stride,
-                                         quality, icc->getData(), icc->getLength())) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-  jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
-                                  .length = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .maxLength = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .colorGamut = yuv420_image.colorGamut};
-
-  // append gain map, no ICC since JPEG encode already did it
-  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
-                            &metadata, dest));
-
-  return NO_ERROR;
-}
-
-/* Encode API-1 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
-  // validate input arguments
-  if (yuv420_image_ptr == nullptr) {
-    ALOGE("received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr for exif metadata");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
-      ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-  jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                            .length = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .maxLength = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
-
-  jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
-  unique_ptr<uint8_t[]> yuv_420_bt601_data;
-  // Convert to bt601 YUV encoding for JPEG encode
-  if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
-    const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
-    yuv_420_bt601_data =
-            make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
-    yuv420_bt601_image.data = yuv_420_bt601_data.get();
-    yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
-    yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
-    yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
-    yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
-
-    {
-      // copy luma
-      uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
-      uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
-      if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
-        memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
-      } else {
-        for (size_t i = 0; i < yuv420_image.height; i++) {
-          memcpy(y_dst, y_src, yuv420_image.width);
-          if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
-            memset(y_dst + yuv420_image.width, 0,
-                   yuv420_bt601_image.luma_stride - yuv420_image.width);
-          }
-          y_dst += yuv420_bt601_image.luma_stride;
-          y_src += yuv420_image.luma_stride;
-        }
-      }
-    }
-
-    if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
-      // copy luma
-      uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
-      uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
-      memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
-    } else {
-      // copy cb & cr
-      uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
-      uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
-      uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
-      uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
-      for (size_t i = 0; i < yuv420_image.height / 2; i++) {
-        memcpy(cb_dst, cb_src, yuv420_image.width / 2);
-        memcpy(cr_dst, cr_src, yuv420_image.width / 2);
-        if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
-          memset(cb_dst + yuv420_image.width / 2, 0,
-                 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
-          memset(cr_dst + yuv420_image.width / 2, 0,
-                 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
-        }
-        cb_dst += yuv420_bt601_image.chroma_stride;
-        cb_src += yuv420_image.chroma_stride;
-        cr_dst += yuv420_bt601_image.chroma_stride;
-        cr_src += yuv420_image.chroma_stride;
-      }
-    }
-    JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
-  }
-
-  // compress 420 image
-  JpegEncoderHelper jpeg_enc_obj_yuv420;
-  if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
-                                         reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
-                                         yuv420_bt601_image.width, yuv420_bt601_image.height,
-                                         yuv420_bt601_image.luma_stride,
-                                         yuv420_bt601_image.chroma_stride, quality, icc->getData(),
-                                         icc->getLength())) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-
-  jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
-                                  .length = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .maxLength = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .colorGamut = yuv420_image.colorGamut};
-
-  // append gain map, no ICC since JPEG encode already did it
-  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
-                            &metadata, dest));
-  return NO_ERROR;
-}
-
-/* Encode API-2 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_uncompressed_ptr yuv420_image_ptr,
-                            jr_compressed_ptr yuv420jpg_image_ptr,
-                            ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
-  // validate input arguments
-  if (yuv420_image_ptr == nullptr) {
-    ALOGE("received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
-      ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-  jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                              .length = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .maxLength = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
-}
-
-/* Encode API-3 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_compressed_ptr yuv420jpg_image_ptr,
-                            ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
-  // validate input arguments
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-
-  // decode input jpeg, gamut is going to be bt601.
-  JpegDecoderHelper jpeg_dec_obj_yuv420;
-  if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
-                                           yuv420jpg_image_ptr->length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  jpegr_uncompressed_struct yuv420_image{};
-  yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
-  yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-  yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-  yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  if (p010_image_ptr->width != yuv420_image.width ||
-      p010_image_ptr->height != yuv420_image.height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
-                              true /* sdr_is_601 */));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                              .length = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .maxLength = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
-}
-
-/* Encode API-4 */
-status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
-                            jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
-                            jr_compressed_ptr dest) {
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed gain map");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (dest == nullptr || dest->data == nullptr) {
-    ALOGE("received nullptr for destination");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // We just want to check if ICC is present, so don't do a full decode. Note,
-  // this doesn't verify that the ICC is valid.
-  JpegDecoderHelper decoder;
-  std::vector<uint8_t> icc;
-  decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
-                                       /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
-                                       /* exifData */ nullptr);
-
-  // Add ICC if not already present.
-  if (icc.size() > 0) {
-    JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
-                              /* icc */ nullptr, /* icc size */ 0, metadata, dest));
-  } else {
-    sp<DataStruct> newIcc =
-            IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
-    JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
-                              newIcc->getData(), newIcc->getLength(), metadata, dest));
-  }
-
-  return NO_ERROR;
-}
-
-status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
-  if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpegr image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (jpeg_image_info_ptr == nullptr) {
-    ALOGE("received nullptr for compressed jpegr info struct");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  jpegr_compressed_struct primary_image, gainmap_image;
-  status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
-  if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
-    return status;
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_hdr;
-  if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
-                                                     &jpeg_image_info_ptr->width,
-                                                     &jpeg_image_info_ptr->height,
-                                                     jpeg_image_info_ptr->iccData,
-                                                     jpeg_image_info_ptr->exifData)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  return status;
-}
-
-/* Decode API */
-status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
-                            float max_display_boost, jr_exif_ptr exif,
-                            ultrahdr_output_format output_format,
-                            jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
-  if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpegr image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (dest == nullptr || dest->data == nullptr) {
-    ALOGE("received nullptr for dest image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (max_display_boost < 1.0f) {
-    ALOGE("received bad value for max_display_boost %f", max_display_boost);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr address for exif data");
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
-    ALOGE("received bad value for output format %d", output_format);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
-  status_t status =
-          extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
-  if (status != NO_ERROR) {
-    if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
-      ALOGE("received invalid compressed jpegr image");
-      return status;
-    }
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_yuv420;
-  if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
-                                           (output_format == ULTRAHDR_OUTPUT_SDR))) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  if (output_format == ULTRAHDR_OUTPUT_SDR) {
-    if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
-         jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
-        jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
-      return ERROR_JPEGR_CALCULATION_ERROR;
-    }
-  } else {
-    if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
-         jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
-        jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
-      return ERROR_JPEGR_CALCULATION_ERROR;
-    }
-  }
-
-  if (exif != nullptr) {
-    if (exif->data == nullptr) {
-      return ERROR_JPEGR_INVALID_NULL_PTR;
-    }
-    if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
-      return ERROR_JPEGR_BUFFER_TOO_SMALL;
-    }
-    memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
-    exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
-  }
-
-  if (output_format == ULTRAHDR_OUTPUT_SDR) {
-    dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-    dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-    memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
-           dest->width * dest->height * 4);
-    return NO_ERROR;
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_gm;
-  if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
-      jpeg_dec_obj_gm.getDecompressedImageSize()) {
-    return ERROR_JPEGR_CALCULATION_ERROR;
-  }
-
-  jpegr_uncompressed_struct gainmap_image;
-  gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
-  gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
-  gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
-
-  if (gainmap_image_ptr != nullptr) {
-    gainmap_image_ptr->width = gainmap_image.width;
-    gainmap_image_ptr->height = gainmap_image.height;
-    int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
-    gainmap_image_ptr->data = malloc(size);
-    memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
-  }
-
-  ultrahdr_metadata_struct uhdr_metadata;
-  if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
-                          jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
-    return ERROR_JPEGR_INVALID_METADATA;
-  }
-
-  if (metadata != nullptr) {
-    metadata->version = uhdr_metadata.version;
-    metadata->minContentBoost = uhdr_metadata.minContentBoost;
-    metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
-    metadata->gamma = uhdr_metadata.gamma;
-    metadata->offsetSdr = uhdr_metadata.offsetSdr;
-    metadata->offsetHdr = uhdr_metadata.offsetHdr;
-    metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
-    metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
-  }
-
-  jpegr_uncompressed_struct yuv420_image;
-  yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
-  yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-  yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-  yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
-                                                         jpeg_dec_obj_yuv420.getICCSize());
-  yuv420_image.luma_stride = yuv420_image.width;
-  uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-  yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-  yuv420_image.chroma_stride = yuv420_image.width >> 1;
-
-  JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
-                           max_display_boost, dest));
-  return NO_ERROR;
-}
-
-status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
-                                JpegEncoderHelper* jpeg_enc_obj_ptr) {
-  if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // Don't need to convert YUV to Bt601 since single channel
-  if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
-                                       gainmap_image_ptr->width, gainmap_image_ptr->height,
-                                       gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
-                                       nullptr, 0)) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-
-  return NO_ERROR;
-}
-
-const int kJobSzInRows = 16;
-static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
-              "align job size to kMapDimensionScaleFactor");
-
-class JobQueue {
-public:
-  bool dequeueJob(size_t& rowStart, size_t& rowEnd);
-  void enqueueJob(size_t rowStart, size_t rowEnd);
-  void markQueueForEnd();
-  void reset();
-
-private:
-  bool mQueuedAllJobs = false;
-  std::deque<std::tuple<size_t, size_t>> mJobs;
-  std::mutex mMutex;
-  std::condition_variable mCv;
-};
-
-bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
-  std::unique_lock<std::mutex> lock{mMutex};
-  while (true) {
-    if (mJobs.empty()) {
-      if (mQueuedAllJobs) {
-        return false;
-      } else {
-        mCv.wait_for(lock, std::chrono::milliseconds(100));
-      }
-    } else {
-      auto it = mJobs.begin();
-      rowStart = std::get<0>(*it);
-      rowEnd = std::get<1>(*it);
-      mJobs.erase(it);
-      return true;
-    }
-  }
-  return false;
-}
-
-void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mJobs.push_back(std::make_tuple(rowStart, rowEnd));
-  lock.unlock();
-  mCv.notify_one();
-}
-
-void JobQueue::markQueueForEnd() {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mQueuedAllJobs = true;
-  lock.unlock();
-  mCv.notify_all();
-}
-
-void JobQueue::reset() {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mJobs.clear();
-  mQueuedAllJobs = false;
-}
-
-status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                                jr_uncompressed_ptr p010_image_ptr,
-                                ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
-                                jr_uncompressed_ptr dest, bool sdr_is_601) {
-  if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr || yuv420_image_ptr->data == nullptr ||
-      yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
-      p010_image_ptr->chroma_data == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420_image_ptr->width != p010_image_ptr->width ||
-      yuv420_image_ptr->height != p010_image_ptr->height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-  if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  size_t image_width = yuv420_image_ptr->width;
-  size_t image_height = yuv420_image_ptr->height;
-  size_t map_width = image_width / kMapDimensionScaleFactor;
-  size_t map_height = image_height / kMapDimensionScaleFactor;
-
-  dest->data = new uint8_t[map_width * map_height];
-  dest->width = map_width;
-  dest->height = map_height;
-  dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  dest->luma_stride = map_width;
-  dest->chroma_data = nullptr;
-  dest->chroma_stride = 0;
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
-
-  ColorTransformFn hdrInvOetf = nullptr;
-  float hdr_white_nits;
-  switch (hdr_tf) {
-    case ULTRAHDR_TF_LINEAR:
-      hdrInvOetf = identityConversion;
-      // Note: this will produce clipping if the input exceeds kHlgMaxNits.
-      // TODO: TF LINEAR will be deprecated.
-      hdr_white_nits = kHlgMaxNits;
-      break;
-    case ULTRAHDR_TF_HLG:
-#if USE_HLG_INVOETF_LUT
-      hdrInvOetf = hlgInvOetfLUT;
-#else
-      hdrInvOetf = hlgInvOetf;
-#endif
-      hdr_white_nits = kHlgMaxNits;
-      break;
-    case ULTRAHDR_TF_PQ:
-#if USE_PQ_INVOETF_LUT
-      hdrInvOetf = pqInvOetfLUT;
-#else
-      hdrInvOetf = pqInvOetf;
-#endif
-      hdr_white_nits = kPqMaxNits;
-      break;
-    default:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_TRANS_FUNC;
-  }
-
-  metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
-  metadata->minContentBoost = 1.0f;
-  metadata->gamma = 1.0f;
-  metadata->offsetSdr = 0.0f;
-  metadata->offsetHdr = 0.0f;
-  metadata->hdrCapacityMin = 1.0f;
-  metadata->hdrCapacityMax = metadata->maxContentBoost;
-
-  float log2MinBoost = log2(metadata->minContentBoost);
-  float log2MaxBoost = log2(metadata->maxContentBoost);
-
-  ColorTransformFn hdrGamutConversionFn =
-          getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
-
-  ColorCalculationFn luminanceFn = nullptr;
-  ColorTransformFn sdrYuvToRgbFn = nullptr;
-  switch (yuv420_image_ptr->colorGamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      luminanceFn = srgbLuminance;
-      sdrYuvToRgbFn = srgbYuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      luminanceFn = p3Luminance;
-      sdrYuvToRgbFn = p3YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      luminanceFn = bt2100Luminance;
-      sdrYuvToRgbFn = bt2100YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-  if (sdr_is_601) {
-    sdrYuvToRgbFn = p3YuvToRgb;
-  }
-
-  ColorTransformFn hdrYuvToRgbFn = nullptr;
-  switch (p010_image_ptr->colorGamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      hdrYuvToRgbFn = srgbYuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      hdrYuvToRgbFn = p3YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      hdrYuvToRgbFn = bt2100YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  std::mutex mutex;
-  const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
-  size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
-  JobQueue jobQueue;
-
-  std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
-                                       hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
-                                       hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
-                                       &jobQueue]() -> void {
-    size_t rowStart, rowEnd;
-    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
-      for (size_t y = rowStart; y < rowEnd; ++y) {
-        for (size_t x = 0; x < dest->width; ++x) {
-          Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
-          Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
-          // We are assuming the SDR input is always sRGB transfer.
-#if USE_SRGB_INVOETF_LUT
-          Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
-#else
-          Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
-#endif
-          float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
-
-          Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
-          Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
-          Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
-          hdr_rgb = hdrGamutConversionFn(hdr_rgb);
-          float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
-
-          size_t pixel_idx = x + y * dest->width;
-          reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-                  encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
-        }
-      }
-    }
-  };
-
-  // generate map
-  std::vector<std::thread> workers;
-  for (int th = 0; th < threads - 1; th++) {
-    workers.push_back(std::thread(generateMap));
-  }
-
-  rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
-  for (size_t rowStart = 0; rowStart < map_height;) {
-    size_t rowEnd = std::min(rowStart + rowStep, map_height);
-    jobQueue.enqueueJob(rowStart, rowEnd);
-    rowStart = rowEnd;
-  }
-  jobQueue.markQueueForEnd();
-  generateMap();
-  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
-
-  map_data.release();
-  return NO_ERROR;
-}
-
-status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                             jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
-                             ultrahdr_output_format output_format, float max_display_boost,
-                             jr_uncompressed_ptr dest) {
-  if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr || yuv420_image_ptr->data == nullptr ||
-      yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (metadata->version.compare(kJpegrVersion)) {
-    ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->gamma != 1.0f) {
-    ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
-    ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->hdrCapacityMin != metadata->minContentBoost ||
-      metadata->hdrCapacityMax != metadata->maxContentBoost) {
-    ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
-          metadata->hdrCapacityMax);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-
-  // TODO: remove once map scaling factor is computed based on actual map dims
-  size_t image_width = yuv420_image_ptr->width;
-  size_t image_height = yuv420_image_ptr->height;
-  size_t map_width = image_width / kMapDimensionScaleFactor;
-  size_t map_height = image_height / kMapDimensionScaleFactor;
-  if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
-    ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
-          "resolution is %dx%d, received gain map resolution is %dx%d",
-          (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  dest->width = yuv420_image_ptr->width;
-  dest->height = yuv420_image_ptr->height;
-  ShepardsIDW idwTable(kMapDimensionScaleFactor);
-  float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
-  GainLUT gainLUT(metadata, display_boost);
-
-  JobQueue jobQueue;
-  std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
-                                       &jobQueue, &idwTable, output_format, &gainLUT,
-                                       display_boost]() -> void {
-    size_t width = yuv420_image_ptr->width;
-    size_t height = yuv420_image_ptr->height;
-
-    size_t rowStart, rowEnd;
-    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
-      for (size_t y = rowStart; y < rowEnd; ++y) {
-        for (size_t x = 0; x < width; ++x) {
-          Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
-          // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
-          Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
-          // We are assuming the SDR base image is always sRGB transfer.
-#if USE_SRGB_INVOETF_LUT
-          Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
-#else
-          Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
-#endif
-          float gain;
-          // TODO: determine map scaling factor based on actual map dims
-          size_t map_scale_factor = kMapDimensionScaleFactor;
-          // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
-          // Currently map_scale_factor is of type size_t, but it could be changed to a float
-          // later.
-          if (map_scale_factor != floorf(map_scale_factor)) {
-            gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
-          } else {
-            gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
-          }
-
-#if USE_APPLY_GAIN_LUT
-          Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
-#else
-          Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
-#endif
-          rgb_hdr = rgb_hdr / display_boost;
-          size_t pixel_idx = x + y * width;
-
-          switch (output_format) {
-            case ULTRAHDR_OUTPUT_HDR_LINEAR: {
-              uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
-              reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
-              break;
-            }
-            case ULTRAHDR_OUTPUT_HDR_HLG: {
-#if USE_HLG_OETF_LUT
-              ColorTransformFn hdrOetf = hlgOetfLUT;
-#else
-              ColorTransformFn hdrOetf = hlgOetf;
-#endif
-              Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
-              uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
-              reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
-              break;
-            }
-            case ULTRAHDR_OUTPUT_HDR_PQ: {
-#if USE_PQ_OETF_LUT
-              ColorTransformFn hdrOetf = pqOetfLUT;
-#else
-              ColorTransformFn hdrOetf = pqOetf;
-#endif
-              Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
-              uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
-              reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
-              break;
-            }
-            default: {
-            }
-              // Should be impossible to hit after input validation.
-          }
-        }
-      }
-    }
-  };
-
-  const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
-  std::vector<std::thread> workers;
-  for (int th = 0; th < threads - 1; th++) {
-    workers.push_back(std::thread(applyRecMap));
-  }
-  const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
-  for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
-    int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
-    jobQueue.enqueueJob(rowStart, rowEnd);
-    rowStart = rowEnd;
-  }
-  jobQueue.markQueueForEnd();
-  applyRecMap();
-  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
-  return NO_ERROR;
-}
-
-status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
-                                              jr_compressed_ptr primary_jpg_image_ptr,
-                                              jr_compressed_ptr gainmap_jpg_image_ptr) {
-  if (jpegr_image_ptr == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  MessageHandler msg_handler;
-  std::shared_ptr<DataSegment> seg =
-          DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
-                              static_cast<const uint8_t*>(jpegr_image_ptr->data),
-                              DataSegment::BufferDispositionPolicy::kDontDelete);
-  DataSegmentDataSource data_source(seg);
-  JpegInfoBuilder jpeg_info_builder;
-  jpeg_info_builder.SetImageLimit(2);
-  JpegScanner jpeg_scanner(&msg_handler);
-  jpeg_scanner.Run(&data_source, &jpeg_info_builder);
-  data_source.Reset();
-
-  if (jpeg_scanner.HasError()) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  const auto& jpeg_info = jpeg_info_builder.GetInfo();
-  const auto& image_ranges = jpeg_info.GetImageRanges();
-
-  if (image_ranges.empty()) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  if (primary_jpg_image_ptr != nullptr) {
-    primary_jpg_image_ptr->data =
-            static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
-    primary_jpg_image_ptr->length = image_ranges[0].GetLength();
-  }
-
-  if (image_ranges.size() == 1) {
-    return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
-  }
-
-  if (gainmap_jpg_image_ptr != nullptr) {
-    gainmap_jpg_image_ptr->data =
-            static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
-    gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
-  }
-
-  // TODO: choose primary image and gain map image carefully
-  if (image_ranges.size() > 2) {
-    ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
-          (int)image_ranges.size());
-  }
-
-  return NO_ERROR;
-}
-
-// JPEG/R structure:
-// SOI (ff d8)
-//
-// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
-// in the JPEG input (Encode API-2, API-3, API-4))
-// APP1 (ff e1)
-// 2 bytes of length (2 + length of exif package)
-// EXIF package (this includes the first two bytes representing the package length)
-//
-// (Required, XMP package) APP1 (ff e1)
-// 2 bytes of length (2 + 29 + length of xmp package)
-// name space ("http://ns.adobe.com/xap/1.0/\0")
-// XMP
-//
-// (Required, MPF package) APP2 (ff e2)
-// 2 bytes of length
-// MPF
-//
-// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
-//
-// SOI (ff d8)
-//
-// (Required, XMP package) APP1 (ff e1)
-// 2 bytes of length (2 + 29 + length of xmp package)
-// name space ("http://ns.adobe.com/xap/1.0/\0")
-// XMP
-//
-// (Required) secondary image (the gain map, without the first two bytes (SOI))
-//
-// Metadata versions we are using:
-// ECMA TR-98 for JFIF marker
-// Exif 2.2 spec for EXIF marker
-// Adobe XMP spec part 3 for XMP marker
-// ICC v4.3 spec for ICC
-status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
-                              jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
-                              void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
-                              jr_compressed_ptr dest) {
-  if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (metadata->version.compare("1.0")) {
-    ALOGE("received bad value for version: %s", metadata->version.c_str());
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->maxContentBoost < metadata->minContentBoost) {
-    ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
-          metadata->maxContentBoost);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
-    ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
-          metadata->hdrCapacityMax);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
-    ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->gamma <= 0.0f) {
-    ALOGE("received bad value for gamma %f", metadata->gamma);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  const string nameSpace = "http://ns.adobe.com/xap/1.0/";
-  const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
-
-  // calculate secondary image length first, because the length will be written into the primary
-  // image xmp
-  const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
-  const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
-          + nameSpaceLength          /* 29 bytes length of name space including \0 */
-          + xmp_secondary.size();    /* length of xmp packet */
-  const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
-          + xmp_secondary_length + gainmap_jpg_image_ptr->length;
-  // primary image
-  const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
-  // same as primary
-  const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
-
-  // Check if EXIF package presents in the JPEG input.
-  // If so, extract and remove the EXIF package.
-  JpegDecoderHelper decoder;
-  if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0};
-  jpegr_compressed_struct new_jpg_image = {.data = nullptr,
-                                           .length = 0,
-                                           .maxLength = 0,
-                                           .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-  std::unique_ptr<uint8_t[]> dest_data;
-  if (decoder.getEXIFPos() >= 0) {
-    if (pExif != nullptr) {
-      ALOGE("received EXIF from outside while the primary image already contains EXIF");
-      return ERROR_JPEGR_INVALID_INPUT_TYPE;
-    }
-    copyJpegWithoutExif(&new_jpg_image,
-                        primary_jpg_image_ptr,
-                        decoder.getEXIFPos(),
-                        decoder.getEXIFSize());
-    dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
-    exif_from_jpg.data = decoder.getEXIFPtr();
-    exif_from_jpg.length = decoder.getEXIFSize();
-    pExif = &exif_from_jpg;
-  }
-
-  jr_compressed_ptr final_primary_jpg_image_ptr =
-          new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
-
-  int pos = 0;
-  // Begin primary image
-  // Write SOI
-  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
-
-  // Write EXIF
-  if (pExif != nullptr) {
-    const int length = 2 + pExif->length;
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
-  }
-
-  // Prepare and write XMP
-  {
-    const int length = xmp_primary_length;
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
-    JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
-  }
-
-  // Write ICC
-  if (pIcc != nullptr && icc_size > 0) {
-    const int length = icc_size + 2;
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
-  }
-
-  // Prepare and write MPF
-  {
-    const int length = 2 + calculateMpfSize();
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
-    // between APP2 + package size + signature
-    // ff e2 00 58 4d 50 46 00
-    // 2 + 2 + 4 = 8 (bytes)
-    // and ff d8 sign of the secondary image
-    int secondary_image_offset = primary_image_size - pos - 8;
-    sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
-                                     secondary_image_size, secondary_image_offset);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
-  }
-
-  // Write primary image
-  JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
-                    final_primary_jpg_image_ptr->length - 2, pos));
-  // Finish primary image
-
-  // Begin secondary image (gain map)
-  // Write SOI
-  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
-
-  // Prepare and write XMP
-  {
-    const int length = xmp_secondary_length;
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
-    JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
-  }
-
-  // Write secondary image
-  JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
-                    gainmap_jpg_image_ptr->length - 2, pos));
-
-  // Set back length
-  dest->length = pos;
-
-  // Done!
-  return NO_ERROR;
-}
-
-status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
-  if (src == nullptr || dest == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (src->width != dest->width || src->height != dest->height) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
-  uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
-  for (size_t y = 0; y < src->height; ++y) {
-    uint16_t* src_y_row = src_y_data + y * src->luma_stride;
-    uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
-    for (size_t x = 0; x < src->width; ++x) {
-      uint16_t y_uint = src_y_row[x] >> 6;
-      dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
-    }
-    if (dest->width != dest->luma_stride) {
-      memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
-    }
-  }
-  uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
-  uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
-  size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
-  uint8_t* dst_v_data = dst_u_data + dst_v_offset;
-  for (size_t y = 0; y < src->height / 2; ++y) {
-    uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
-    uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
-    uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
-    for (size_t x = 0; x < src->width / 2; ++x) {
-      uint16_t u_uint = src_uv_row[x << 1] >> 6;
-      uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
-      dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
-      dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
-    }
-    if (dest->width / 2 != dest->chroma_stride) {
-      memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
-      memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
-    }
-  }
-  dest->colorGamut = src->colorGamut;
-  return NO_ERROR;
-}
-
-status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
-                           ultrahdr_color_gamut dest_encoding) {
-  if (image == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  ColorTransformFn conversionFn = nullptr;
-  switch (src_encoding) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return NO_ERROR;
-        case ULTRAHDR_COLORGAMUT_P3:
-          conversionFn = yuv709To601;
-          break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          conversionFn = yuv709To2100;
-          break;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          conversionFn = yuv601To709;
-          break;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return NO_ERROR;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          conversionFn = yuv601To2100;
-          break;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          conversionFn = yuv2100To709;
-          break;
-        case ULTRAHDR_COLORGAMUT_P3:
-          conversionFn = yuv2100To601;
-          break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return NO_ERROR;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    default:
-      // Should be impossible to hit after input validation
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  if (conversionFn == nullptr) {
-    // Should be impossible to hit after input validation
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  for (size_t y = 0; y < image->height / 2; ++y) {
-    for (size_t x = 0; x < image->width / 2; ++x) {
-      transformYuv420(image, x, y, conversionFn);
-    }
-  }
-
-  return NO_ERROR;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp
deleted file mode 100644
index c434eb6..0000000
--- a/libs/ultrahdr/jpegrutils.cpp
+++ /dev/null
@@ -1,600 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <ultrahdr/jpegrutils.h>
-
-#include <algorithm>
-#include <cmath>
-
-#include <image_io/xml/xml_reader.h>
-#include <image_io/xml/xml_writer.h>
-#include <image_io/base/message_handler.h>
-#include <image_io/xml/xml_element_rules.h>
-#include <image_io/xml/xml_handler.h>
-#include <image_io/xml/xml_rule.h>
-#include <utils/Log.h>
-
-using namespace photos_editing_formats::image_io;
-using namespace std;
-
-namespace android::ultrahdr {
-/*
- * Helper function used for generating XMP metadata.
- *
- * @param prefix The prefix part of the name.
- * @param suffix The suffix part of the name.
- * @return A name of the form "prefix:suffix".
- */
-static inline string Name(const string &prefix, const string &suffix) {
-  std::stringstream ss;
-  ss << prefix << ":" << suffix;
-  return ss.str();
-}
-
-DataStruct::DataStruct(int s) {
-    data = malloc(s);
-    length = s;
-    memset(data, 0, s);
-    writePos = 0;
-}
-
-DataStruct::~DataStruct() {
-    if (data != nullptr) {
-        free(data);
-    }
-}
-
-void* DataStruct::getData() {
-    return data;
-}
-
-int DataStruct::getLength() {
-    return length;
-}
-
-int DataStruct::getBytesWritten() {
-    return writePos;
-}
-
-bool DataStruct::write8(uint8_t value) {
-    uint8_t v = value;
-    return write(&v, 1);
-}
-
-bool DataStruct::write16(uint16_t value) {
-    uint16_t v = value;
-    return write(&v, 2);
-}
-bool DataStruct::write32(uint32_t value) {
-    uint32_t v = value;
-    return write(&v, 4);
-}
-
-bool DataStruct::write(const void* src, int size) {
-    if (writePos + size > length) {
-        ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d",
-                writePos, size, length);
-        return false;
-    }
-    memcpy((uint8_t*) data + writePos, src, size);
-    writePos += size;
-    return true;
-}
-
-/*
- * Helper function used for writing data to destination.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
-  if (position + length > destination->maxLength) {
-    return ERROR_JPEGR_BUFFER_TOO_SMALL;
-  }
-
-  memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
-  position += length;
-  return NO_ERROR;
-}
-
-// Extremely simple XML Handler - just searches for interesting elements
-class XMPXmlHandler : public XmlHandler {
-public:
-
-    XMPXmlHandler() : XmlHandler() {
-        state = NotStrarted;
-        versionFound = false;
-        minContentBoostFound = false;
-        maxContentBoostFound = false;
-        gammaFound = false;
-        offsetSdrFound = false;
-        offsetHdrFound = false;
-        hdrCapacityMinFound = false;
-        hdrCapacityMaxFound = false;
-        baseRenditionIsHdrFound = false;
-    }
-
-    enum ParseState {
-        NotStrarted,
-        Started,
-        Done
-    };
-
-    virtual DataMatchResult StartElement(const XmlTokenContext& context) {
-        string val;
-        if (context.BuildTokenValue(&val)) {
-            if (!val.compare(containerName)) {
-                state = Started;
-            } else {
-                if (state != Done) {
-                    state = NotStrarted;
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
-        if (state == Started) {
-            state = Done;
-            lastAttributeName = "";
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
-        string val;
-        if (state == Started) {
-            if (context.BuildTokenValue(&val)) {
-                if (!val.compare(versionAttrName)) {
-                    lastAttributeName = versionAttrName;
-                } else if (!val.compare(maxContentBoostAttrName)) {
-                    lastAttributeName = maxContentBoostAttrName;
-                } else if (!val.compare(minContentBoostAttrName)) {
-                    lastAttributeName = minContentBoostAttrName;
-                } else if (!val.compare(gammaAttrName)) {
-                    lastAttributeName = gammaAttrName;
-                } else if (!val.compare(offsetSdrAttrName)) {
-                    lastAttributeName = offsetSdrAttrName;
-                } else if (!val.compare(offsetHdrAttrName)) {
-                    lastAttributeName = offsetHdrAttrName;
-                } else if (!val.compare(hdrCapacityMinAttrName)) {
-                    lastAttributeName = hdrCapacityMinAttrName;
-                } else if (!val.compare(hdrCapacityMaxAttrName)) {
-                    lastAttributeName = hdrCapacityMaxAttrName;
-                } else if (!val.compare(baseRenditionIsHdrAttrName)) {
-                    lastAttributeName = baseRenditionIsHdrAttrName;
-                } else {
-                    lastAttributeName = "";
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
-        string val;
-        if (state == Started) {
-            if (context.BuildTokenValue(&val, true)) {
-                if (!lastAttributeName.compare(versionAttrName)) {
-                    versionStr = val;
-                    versionFound = true;
-                } else if (!lastAttributeName.compare(maxContentBoostAttrName)) {
-                    maxContentBoostStr = val;
-                    maxContentBoostFound = true;
-                } else if (!lastAttributeName.compare(minContentBoostAttrName)) {
-                    minContentBoostStr = val;
-                    minContentBoostFound = true;
-                } else if (!lastAttributeName.compare(gammaAttrName)) {
-                    gammaStr = val;
-                    gammaFound = true;
-                } else if (!lastAttributeName.compare(offsetSdrAttrName)) {
-                    offsetSdrStr = val;
-                    offsetSdrFound = true;
-                } else if (!lastAttributeName.compare(offsetHdrAttrName)) {
-                    offsetHdrStr = val;
-                    offsetHdrFound = true;
-                } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) {
-                    hdrCapacityMinStr = val;
-                    hdrCapacityMinFound = true;
-                } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) {
-                    hdrCapacityMaxStr = val;
-                    hdrCapacityMaxFound = true;
-                } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) {
-                    baseRenditionIsHdrStr = val;
-                    baseRenditionIsHdrFound = true;
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    bool getVersion(string* version, bool* present) {
-        if (state == Done) {
-            *version = versionStr;
-            *present = versionFound;
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    bool getMaxContentBoost(float* max_content_boost, bool* present) {
-        if (state == Done) {
-            *present = maxContentBoostFound;
-            stringstream ss(maxContentBoostStr);
-            float val;
-            if (ss >> val) {
-                *max_content_boost = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    bool getMinContentBoost(float* min_content_boost, bool* present) {
-        if (state == Done) {
-            *present = minContentBoostFound;
-            stringstream ss(minContentBoostStr);
-            float val;
-            if (ss >> val) {
-                *min_content_boost = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    bool getGamma(float* gamma, bool* present) {
-        if (state == Done) {
-            *present = gammaFound;
-            stringstream ss(gammaStr);
-            float val;
-            if (ss >> val) {
-                *gamma = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getOffsetSdr(float* offset_sdr, bool* present) {
-        if (state == Done) {
-            *present = offsetSdrFound;
-            stringstream ss(offsetSdrStr);
-            float val;
-            if (ss >> val) {
-                *offset_sdr = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getOffsetHdr(float* offset_hdr, bool* present) {
-        if (state == Done) {
-            *present = offsetHdrFound;
-            stringstream ss(offsetHdrStr);
-            float val;
-            if (ss >> val) {
-                *offset_hdr = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) {
-        if (state == Done) {
-            *present = hdrCapacityMinFound;
-            stringstream ss(hdrCapacityMinStr);
-            float val;
-            if (ss >> val) {
-                *hdr_capacity_min = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) {
-        if (state == Done) {
-            *present = hdrCapacityMaxFound;
-            stringstream ss(hdrCapacityMaxStr);
-            float val;
-            if (ss >> val) {
-                *hdr_capacity_max = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) {
-        if (state == Done) {
-            *present = baseRenditionIsHdrFound;
-            if (!baseRenditionIsHdrStr.compare("False")) {
-                *base_rendition_is_hdr = false;
-                return true;
-            } else if (!baseRenditionIsHdrStr.compare("True")) {
-                *base_rendition_is_hdr = true;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-
-private:
-    static const string containerName;
-
-    static const string versionAttrName;
-    string              versionStr;
-    bool                versionFound;
-    static const string maxContentBoostAttrName;
-    string              maxContentBoostStr;
-    bool                maxContentBoostFound;
-    static const string minContentBoostAttrName;
-    string              minContentBoostStr;
-    bool                minContentBoostFound;
-    static const string gammaAttrName;
-    string              gammaStr;
-    bool                gammaFound;
-    static const string offsetSdrAttrName;
-    string              offsetSdrStr;
-    bool                offsetSdrFound;
-    static const string offsetHdrAttrName;
-    string              offsetHdrStr;
-    bool                offsetHdrFound;
-    static const string hdrCapacityMinAttrName;
-    string              hdrCapacityMinStr;
-    bool                hdrCapacityMinFound;
-    static const string hdrCapacityMaxAttrName;
-    string              hdrCapacityMaxStr;
-    bool                hdrCapacityMaxFound;
-    static const string baseRenditionIsHdrAttrName;
-    string              baseRenditionIsHdrStr;
-    bool                baseRenditionIsHdrFound;
-
-    string              lastAttributeName;
-    ParseState          state;
-};
-
-// GContainer XMP constants - URI and namespace prefix
-const string kContainerUri        = "http://ns.google.com/photos/1.0/container/";
-const string kContainerPrefix     = "Container";
-
-// GContainer XMP constants - element and attribute names
-const string kConDirectory            = Name(kContainerPrefix, "Directory");
-const string kConItem                 = Name(kContainerPrefix, "Item");
-
-// GContainer XMP constants - names for XMP handlers
-const string XMPXmlHandler::containerName = "rdf:Description";
-// Item XMP constants - URI and namespace prefix
-const string kItemUri        = "http://ns.google.com/photos/1.0/container/item/";
-const string kItemPrefix     = "Item";
-
-// Item XMP constants - element and attribute names
-const string kItemLength           = Name(kItemPrefix, "Length");
-const string kItemMime             = Name(kItemPrefix, "Mime");
-const string kItemSemantic         = Name(kItemPrefix, "Semantic");
-
-// Item XMP constants - element and attribute values
-const string kSemanticPrimary = "Primary";
-const string kSemanticGainMap = "GainMap";
-const string kMimeImageJpeg   = "image/jpeg";
-
-// GainMap XMP constants - URI and namespace prefix
-const string kGainMapUri      = "http://ns.adobe.com/hdr-gain-map/1.0/";
-const string kGainMapPrefix   = "hdrgm";
-
-// GainMap XMP constants - element and attribute names
-const string kMapVersion            = Name(kGainMapPrefix, "Version");
-const string kMapGainMapMin         = Name(kGainMapPrefix, "GainMapMin");
-const string kMapGainMapMax         = Name(kGainMapPrefix, "GainMapMax");
-const string kMapGamma              = Name(kGainMapPrefix, "Gamma");
-const string kMapOffsetSdr          = Name(kGainMapPrefix, "OffsetSDR");
-const string kMapOffsetHdr          = Name(kGainMapPrefix, "OffsetHDR");
-const string kMapHDRCapacityMin     = Name(kGainMapPrefix, "HDRCapacityMin");
-const string kMapHDRCapacityMax     = Name(kGainMapPrefix, "HDRCapacityMax");
-const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR");
-
-// GainMap XMP constants - names for XMP handlers
-const string XMPXmlHandler::versionAttrName = kMapVersion;
-const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
-const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
-const string XMPXmlHandler::gammaAttrName = kMapGamma;
-const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr;
-const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr;
-const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin;
-const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax;
-const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR;
-
-bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) {
-    string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-
-    if (xmp_size < nameSpace.size()+2) {
-        // Data too short
-        return false;
-    }
-
-    if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
-        // Not correct namespace
-        return false;
-    }
-
-    // Position the pointers to the start of XMP XML portion
-    xmp_data += nameSpace.size()+1;
-    xmp_size -= nameSpace.size()+1;
-    XMPXmlHandler handler;
-
-    // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
-    while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
-        xmp_size--;
-    }
-
-    string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
-    MessageHandler msg_handler;
-    unique_ptr<XmlRule> rule(new XmlElementRule);
-    XmlReader reader(&handler, &msg_handler);
-    reader.StartParse(std::move(rule));
-    reader.Parse(str);
-    reader.FinishParse();
-    if (reader.HasErrors()) {
-        // Parse error
-        return false;
-    }
-
-    // Apply default values to any not-present fields, except for Version,
-    // maxContentBoost, and hdrCapacityMax, which are required. Return false if
-    // we encounter a present field that couldn't be parsed, since this
-    // indicates it is invalid (eg. string where there should be a float).
-    bool present = false;
-    if (!handler.getVersion(&metadata->version, &present) || !present) {
-        return false;
-    }
-    if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) {
-        return false;
-    }
-    if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) {
-        return false;
-    }
-    if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) {
-        if (present) return false;
-        metadata->minContentBoost = 1.0f;
-    }
-    if (!handler.getGamma(&metadata->gamma, &present)) {
-        if (present) return false;
-        metadata->gamma = 1.0f;
-    }
-    if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) {
-        if (present) return false;
-        metadata->offsetSdr = 1.0f / 64.0f;
-    }
-    if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) {
-        if (present) return false;
-        metadata->offsetHdr = 1.0f / 64.0f;
-    }
-    if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) {
-        if (present) return false;
-        metadata->hdrCapacityMin = 1.0f;
-    }
-
-    bool base_rendition_is_hdr;
-    if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) {
-        if (present) return false;
-        base_rendition_is_hdr = false;
-    }
-    if (base_rendition_is_hdr) {
-        ALOGE("Base rendition of HDR is not supported!");
-        return false;
-    }
-
-    return true;
-}
-
-string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) {
-  const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
-  const vector<string> kLiItem({string("rdf:li"), kConItem});
-
-  std::stringstream ss;
-  photos_editing_formats::image_io::XmlWriter writer(ss);
-  writer.StartWritingElement("x:xmpmeta");
-  writer.WriteXmlns("x", "adobe:ns:meta/");
-  writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
-  writer.StartWritingElement("rdf:RDF");
-  writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-  writer.StartWritingElement("rdf:Description");
-  writer.WriteXmlns(kContainerPrefix, kContainerUri);
-  writer.WriteXmlns(kItemPrefix, kItemUri);
-  writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
-  writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
-
-  writer.StartWritingElements(kConDirSeq);
-
-  size_t item_depth = writer.StartWritingElement("rdf:li");
-  writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
-  writer.StartWritingElement(kConItem);
-  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
-  writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
-  writer.FinishWritingElementsToDepth(item_depth);
-
-  writer.StartWritingElement("rdf:li");
-  writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
-  writer.StartWritingElement(kConItem);
-  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap);
-  writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
-  writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
-
-  writer.FinishWriting();
-
-  return ss.str();
-}
-
-string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) {
-  const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
-
-  std::stringstream ss;
-  photos_editing_formats::image_io::XmlWriter writer(ss);
-  writer.StartWritingElement("x:xmpmeta");
-  writer.WriteXmlns("x", "adobe:ns:meta/");
-  writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
-  writer.StartWritingElement("rdf:RDF");
-  writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-  writer.StartWritingElement("rdf:Description");
-  writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
-  writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
-  writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost));
-  writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost));
-  writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma);
-  writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr);
-  writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr);
-  writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin));
-  writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax));
-  writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False");
-  writer.FinishWriting();
-
-  return ss.str();
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp
deleted file mode 100644
index f1679ef..0000000
--- a/libs/ultrahdr/multipictureformat.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <ultrahdr/multipictureformat.h>
-#include <ultrahdr/jpegrutils.h>
-
-namespace android::ultrahdr {
-size_t calculateMpfSize() {
-    return sizeof(kMpfSig) +                 // Signature
-            kMpEndianSize +                   // Endianness
-            sizeof(uint32_t) +                // Index IFD Offset
-            sizeof(uint16_t) +                // Tag count
-            kTagSerializedCount * kTagSize +  // 3 tags at 12 bytes each
-            sizeof(uint32_t) +                // Attribute IFD offset
-            kNumPictures * kMPEntrySize;      // MP Entries for each image
-}
-
-sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
-        int secondary_image_size, int secondary_image_offset) {
-    size_t mpf_size = calculateMpfSize();
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(mpf_size);
-
-    dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig));
-#if USE_BIG_ENDIAN
-    dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize);
-#else
-    dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize);
-#endif
-
-    // Set the Index IFD offset be the position after the endianness value and this offset.
-    constexpr uint32_t indexIfdOffset =
-            static_cast<uint16_t>(kMpEndianSize + sizeof(kMpfSig));
-    dataStruct->write32(Endian_SwapBE32(indexIfdOffset));
-
-    // We will write 3 tags (version, number of images, MP entries).
-    dataStruct->write16(Endian_SwapBE16(kTagSerializedCount));
-
-    // Write the version tag.
-    dataStruct->write16(Endian_SwapBE16(kVersionTag));
-    dataStruct->write16(Endian_SwapBE16(kVersionType));
-    dataStruct->write32(Endian_SwapBE32(kVersionCount));
-    dataStruct->write(kVersionExpected, kVersionSize);
-
-    // Write the number of images.
-    dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag));
-    dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType));
-    dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount));
-    dataStruct->write32(Endian_SwapBE32(kNumPictures));
-
-    // Write the MP entries.
-    dataStruct->write16(Endian_SwapBE16(kMPEntryTag));
-    dataStruct->write16(Endian_SwapBE16(kMPEntryType));
-    dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures));
-    const uint32_t mpEntryOffset =
-            static_cast<uint32_t>(dataStruct->getBytesWritten() -  // The bytes written so far
-                                  sizeof(kMpfSig) +   // Excluding the MPF signature
-                                  sizeof(uint32_t) +  // The 4 bytes for this offset
-                                  sizeof(uint32_t));  // The 4 bytes for the attribute IFD offset.
-    dataStruct->write32(Endian_SwapBE32(mpEntryOffset));
-
-    // Write the attribute IFD offset (zero because we don't write it).
-    dataStruct->write32(0);
-
-    // Write the MP entries for primary image
-    dataStruct->write32(
-            Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary));
-    dataStruct->write32(Endian_SwapBE32(primary_image_size));
-    dataStruct->write32(Endian_SwapBE32(primary_image_offset));
-    dataStruct->write16(0);
-    dataStruct->write16(0);
-
-    // Write the MP entries for secondary image
-    dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg));
-    dataStruct->write32(Endian_SwapBE32(secondary_image_size));
-    dataStruct->write32(Endian_SwapBE32(secondary_image_offset));
-    dataStruct->write16(0);
-    dataStruct->write16(0);
-
-    return dataStruct;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/AndroidTest.xml b/libs/ultrahdr/tests/AndroidTest.xml
deleted file mode 100644
index 1754a5c..0000000
--- a/libs/ultrahdr/tests/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the"License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an"AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Unit test configuration for ultrahdr_unit_test">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push-file" key="ultrahdr_unit_test" value="/data/local/tmp/ultrahdr_unit_test" />
-        <option name="push" value="data/*->/data/local/tmp/" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="ultrahdr_unit_test" />
-    </test>
-</configuration>
diff --git a/libs/ultrahdr/tests/data/jpeg_image.jpg b/libs/ultrahdr/tests/data/jpeg_image.jpg
deleted file mode 100644
index e285742..0000000
--- a/libs/ultrahdr/tests/data/jpeg_image.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-318x240.yu12 b/libs/ultrahdr/tests/data/minnie-318x240.yu12
deleted file mode 100644
index 7b2fc71..0000000
--- a/libs/ultrahdr/tests/data/minnie-318x240.yu12
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-y.jpg b/libs/ultrahdr/tests/data/minnie-320x240-y.jpg
deleted file mode 100644
index 20b5a2c..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-y.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
deleted file mode 100644
index c7f4538..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
deleted file mode 100644
index 41300f4..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240.y b/libs/ultrahdr/tests/data/minnie-320x240.y
deleted file mode 100644
index f9d8371..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240.y
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240.yu12 b/libs/ultrahdr/tests/data/minnie-320x240.yu12
deleted file mode 100644
index 0d66f53..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240.yu12
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/raw_p010_image.p010 b/libs/ultrahdr/tests/data/raw_p010_image.p010
deleted file mode 100644
index 01673bf..0000000
--- a/libs/ultrahdr/tests/data/raw_p010_image.p010
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 b/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420
deleted file mode 100644
index c043da6..0000000
--- a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp
deleted file mode 100644
index 7c2d076..0000000
--- a/libs/ultrahdr/tests/gainmapmath_test.cpp
+++ /dev/null
@@ -1,1359 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cmath>
-#include <gtest/gtest.h>
-#include <gmock/gmock.h>
-#include <ultrahdr/gainmapmath.h>
-
-namespace android::ultrahdr {
-
-class GainMapMathTest : public testing::Test {
-public:
-  GainMapMathTest();
-  ~GainMapMathTest();
-
-  float ComparisonEpsilon() { return 1e-4f; }
-  float LuminanceEpsilon() { return 1e-2f; }
-  float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); }
-
-  Color Yuv420(uint8_t y, uint8_t u, uint8_t v) {
-      return {{{ static_cast<float>(y) / 255.0f,
-                 (static_cast<float>(u) - 128.0f) / 255.0f,
-                 (static_cast<float>(v) - 128.0f) / 255.0f }}};
-  }
-
-  Color P010(uint16_t y, uint16_t u, uint16_t v) {
-      return {{{ (static_cast<float>(y) - 64.0f) / 876.0f,
-                 (static_cast<float>(u) - 64.0f) / 896.0f - 0.5f,
-                 (static_cast<float>(v) - 64.0f) / 896.0f - 0.5f }}};
-  }
-
-  float Map(uint8_t e) {
-    return static_cast<float>(e) / 255.0f;
-  }
-
-  Color ColorMin(Color e1, Color e2) {
-    return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}};
-  }
-
-  Color ColorMax(Color e1, Color e2) {
-    return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}};
-  }
-
-  Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
-  Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; }
-
-  Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
-  Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; }
-  Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; }
-
-  Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
-  Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
-
-  Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; }
-  Color SrgbYuvGreen() { return {{{ 0.7152f, -0.38543f, -0.45415f }}}; }
-  Color SrgbYuvBlue() { return {{{ 0.0722f, 0.5f, -0.04585f }}}; }
-
-  Color P3YuvRed() { return {{{ 0.299f, -0.16874f, 0.5f }}}; }
-  Color P3YuvGreen() { return {{{ 0.587f, -0.33126f, -0.41869f }}}; }
-  Color P3YuvBlue() { return {{{ 0.114f, 0.5f, -0.08131f }}}; }
-
-  Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; }
-  Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; }
-  Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; }
-
-  float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
-    Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * kSdrWhiteNits;
-  }
-
-  float P3YuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
-    Color rgb_gamma = p3YuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * kSdrWhiteNits;
-  }
-
-  float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf,
-                             ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn,
-                             float scale_factor) {
-    Color rgb_gamma = bt2100YuvToRgb(yuv_gamma);
-    Color rgb = hdrInvOetf(rgb_gamma);
-    rgb = gamutConversionFn(rgb);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * scale_factor;
-  }
-
-  Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) {
-    Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    return applyGain(rgb, gain, metadata);
-  }
-
-  jpegr_uncompressed_struct Yuv420Image() {
-    static uint8_t pixels[] = {
-      // Y
-      0x00, 0x10, 0x20, 0x30,
-      0x01, 0x11, 0x21, 0x31,
-      0x02, 0x12, 0x22, 0x32,
-      0x03, 0x13, 0x23, 0x33,
-      // U
-      0xA0, 0xA1,
-      0xA2, 0xA3,
-      // V
-      0xB0, 0xB1,
-      0xB2, 0xB3,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2 };
-  }
-
-  Color (*Yuv420Colors())[4] {
-    static Color colors[4][4] = {
-      {
-        Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0),
-        Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1),
-      }, {
-        Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0),
-        Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1),
-      }, {
-        Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2),
-        Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3),
-      }, {
-        Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2),
-        Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3),
-      },
-    };
-    return colors;
-  }
-
-  jpegr_uncompressed_struct P010Image() {
-    static uint16_t pixels[] = {
-      // Y
-      0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6,
-      0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6,
-      0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6,
-      0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6,
-      // UV
-      0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
-      0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4 };
-  }
-
-  Color (*P010Colors())[4] {
-    static Color colors[4][4] = {
-      {
-        P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0),
-        P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1),
-      }, {
-        P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0),
-        P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1),
-      }, {
-        P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2),
-        P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3),
-      }, {
-        P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2),
-        P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3),
-      },
-    };
-    return colors;
-  }
-
-  jpegr_uncompressed_struct MapImage() {
-    static uint8_t pixels[] = {
-      0x00, 0x10, 0x20, 0x30,
-      0x01, 0x11, 0x21, 0x31,
-      0x02, 0x12, 0x22, 0x32,
-      0x03, 0x13, 0x23, 0x33,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED };
-  }
-
-  float (*MapValues())[4] {
-    static float values[4][4] = {
-      {
-        Map(0x00), Map(0x10), Map(0x20), Map(0x30),
-      }, {
-        Map(0x01), Map(0x11), Map(0x21), Map(0x31),
-      }, {
-        Map(0x02), Map(0x12), Map(0x22), Map(0x32),
-      }, {
-        Map(0x03), Map(0x13), Map(0x23), Map(0x33),
-      },
-    };
-    return values;
-  }
-
-protected:
-  virtual void SetUp();
-  virtual void TearDown();
-};
-
-GainMapMathTest::GainMapMathTest() {}
-GainMapMathTest::~GainMapMathTest() {}
-
-void GainMapMathTest::SetUp() {}
-void GainMapMathTest::TearDown() {}
-
-#define EXPECT_RGB_EQ(e1, e2)       \
-  EXPECT_FLOAT_EQ((e1).r, (e2).r);  \
-  EXPECT_FLOAT_EQ((e1).g, (e2).g);  \
-  EXPECT_FLOAT_EQ((e1).b, (e2).b)
-
-#define EXPECT_RGB_NEAR(e1, e2)                     \
-  EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon())
-
-#define EXPECT_RGB_CLOSE(e1, e2)                            \
-  EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \
-  EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \
-  EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f)
-
-#define EXPECT_YUV_EQ(e1, e2)       \
-  EXPECT_FLOAT_EQ((e1).y, (e2).y);  \
-  EXPECT_FLOAT_EQ((e1).u, (e2).u);  \
-  EXPECT_FLOAT_EQ((e1).v, (e2).v)
-
-#define EXPECT_YUV_NEAR(e1, e2)                     \
-  EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon())
-
-#define EXPECT_YUV_BETWEEN(e, min, max)                                           \
-  EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \
-  EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \
-  EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v)))
-
-// TODO: a bunch of these tests can be parameterized.
-
-TEST_F(GainMapMathTest, ColorConstruct) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  EXPECT_FLOAT_EQ(e1.r, 0.1f);
-  EXPECT_FLOAT_EQ(e1.g, 0.2f);
-  EXPECT_FLOAT_EQ(e1.b, 0.3f);
-
-  EXPECT_FLOAT_EQ(e1.y, 0.1f);
-  EXPECT_FLOAT_EQ(e1.u, 0.2f);
-  EXPECT_FLOAT_EQ(e1.v, 0.3f);
-}
-
-TEST_F(GainMapMathTest, ColorAddColor) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 + e1;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
-
-  e2 += e1;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
-}
-
-TEST_F(GainMapMathTest, ColorAddFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 + 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f);
-
-  e2 += 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
-}
-
-TEST_F(GainMapMathTest, ColorSubtractColor) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 - e1;
-  EXPECT_FLOAT_EQ(e2.r, 0.0f);
-  EXPECT_FLOAT_EQ(e2.g, 0.0f);
-  EXPECT_FLOAT_EQ(e2.b, 0.0f);
-
-  e2 -= e1;
-  EXPECT_FLOAT_EQ(e2.r, -e1.r);
-  EXPECT_FLOAT_EQ(e2.g, -e1.g);
-  EXPECT_FLOAT_EQ(e2.b, -e1.b);
-}
-
-TEST_F(GainMapMathTest, ColorSubtractFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 - 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f);
-
-  e2 -= 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
-}
-
-TEST_F(GainMapMathTest, ColorMultiplyFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 * 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
-
-  e2 *= 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
-}
-
-TEST_F(GainMapMathTest, ColorDivideFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 / 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f);
-
-  e2 /= 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
-}
-
-TEST_F(GainMapMathTest, SrgbLuminance) {
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
-}
-
-TEST_F(GainMapMathTest, SrgbYuvToRgb) {
-  Color rgb_black = srgbYuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = srgbYuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = srgbYuvToRgb(SrgbYuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = srgbYuvToRgb(SrgbYuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = srgbYuvToRgb(SrgbYuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, SrgbRgbToYuv) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) {
-  Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, SrgbTransferFunction) {
-  EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f);
-  EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon());
-  EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon());
-  EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
-}
-
-TEST_F(GainMapMathTest, P3Luminance) {
-  EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
-}
-
-TEST_F(GainMapMathTest, P3YuvToRgb) {
-  Color rgb_black = p3YuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = p3YuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = p3YuvToRgb(P3YuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = p3YuvToRgb(P3YuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = p3YuvToRgb(P3YuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, P3RgbToYuv) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, P3YuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, P3YuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) {
-  Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-TEST_F(GainMapMathTest, Bt2100Luminance) {
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
-}
-
-TEST_F(GainMapMathTest, Bt2100YuvToRgb) {
-  Color rgb_black = bt2100YuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = bt2100YuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100RgbToYuv) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) {
-  Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, Bt709ToBt601YuvConversion) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_black), YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_white), YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_r), P3YuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_g), P3YuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_b), P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt709ToBt2100YuvConversion) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_black), YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_white), YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_r), Bt2100YuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_g), Bt2100YuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_b), Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt601ToBt709YuvConversion) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_black), YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_white), YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_r), SrgbYuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_g), SrgbYuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_b), SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt601ToBt2100YuvConversion) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_black), YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_white), YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_r), Bt2100YuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_g), Bt2100YuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_b), Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100ToBt709YuvConversion) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_black), YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_white), YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_r), SrgbYuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_g), SrgbYuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_b), SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_black), YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_white), YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_r), P3YuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_g), P3YuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_b), P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, TransformYuv420) {
-  ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100,
-                                    yuv2100To709, yuv2100To601 };
-  for (const ColorTransformFn& transform : transforms) {
-    jpegr_uncompressed_struct input = Yuv420Image();
-
-    size_t out_buf_size = input.width * input.height * 3 / 2;
-    std::unique_ptr<uint8_t[]> out_buf = std::make_unique<uint8_t[]>(out_buf_size);
-    memcpy(out_buf.get(), input.data, out_buf_size);
-    jpegr_uncompressed_struct output = Yuv420Image();
-    output.data = out_buf.get();
-    output.chroma_data = out_buf.get() + input.width * input.height;
-    output.luma_stride = input.width;
-    output.chroma_stride = input.width / 2;
-
-    transformYuv420(&output, 1, 1, transform);
-
-    for (size_t y = 0; y < 4; ++y) {
-      for (size_t x = 0; x < 4; ++x) {
-        // Skip the last chroma sample, which we modified above
-        if (x >= 2 && y >= 2) {
-          continue;
-        }
-
-        // All other pixels should remain unchanged
-        EXPECT_YUV_EQ(getYuv420Pixel(&input, x, y), getYuv420Pixel(&output, x, y));
-      }
-    }
-
-    // modified pixels should be updated as intended by the transformYuv420 algorithm
-    Color in1 = getYuv420Pixel(&input,   2, 2);
-    Color in2 = getYuv420Pixel(&input,   3, 2);
-    Color in3 = getYuv420Pixel(&input,   2, 3);
-    Color in4 = getYuv420Pixel(&input,   3, 3);
-    Color out1 = getYuv420Pixel(&output, 2, 2);
-    Color out2 = getYuv420Pixel(&output, 3, 2);
-    Color out3 = getYuv420Pixel(&output, 2, 3);
-    Color out4 = getYuv420Pixel(&output, 3, 3);
-
-    EXPECT_NEAR(transform(in1).y, out1.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in2).y, out2.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in3).y, out3.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in4).y, out4.y, YuvConversionEpsilon());
-
-    Color expect_uv = (transform(in1) + transform(in2) + transform(in3) + transform(in4)) / 4.0f;
-
-    EXPECT_NEAR(expect_uv.u, out1.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out2.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out3.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out4.u, YuvConversionEpsilon());
-
-    EXPECT_NEAR(expect_uv.v, out1.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out2.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out3.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out4.v, YuvConversionEpsilon());
-  }
-}
-
-TEST_F(GainMapMathTest, HlgOetf) {
-  EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
-  EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f);
-
-  Color e = {{{ 0.04167f, 0.08333f, 0.5f }}};
-  Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}};
-  EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
-}
-
-TEST_F(GainMapMathTest, HlgInvOetf) {
-  EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f);
-  EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f);
-
-  Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}};
-  Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}};
-  EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
-}
-
-TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) {
-  EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f);
-  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
-}
-
-TEST_F(GainMapMathTest, PqOetf) {
-  EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f);
-  EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon());
-  EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon());
-  EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f);
-
-  Color e = {{{ 0.01f, 0.5f, 0.99f }}};
-  Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}};
-  EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
-}
-
-TEST_F(GainMapMathTest, PqInvOetf) {
-  EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f);
-  EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon());
-  EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon());
-  EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f);
-
-  Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}};
-  Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}};
-  EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
-}
-
-TEST_F(GainMapMathTest, PqInvOetfLUT) {
-    for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, HlgInvOetfLUT) {
-    for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, pqOetfLUT) {
-    for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, hlgOetfLUT) {
-    for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, srgbInvOetfLUT) {
-    for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, applyGainLUT) {
-  for (int boost = 1; boost <= 10; boost++) {
-    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
-                                       .minContentBoost = 1.0f / static_cast<float>(boost) };
-    GainLUT gainLUT(&metadata);
-    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
-                      applyGainLUT(RgbBlack(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
-                      applyGainLUT(RgbWhite(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
-                      applyGainLUT(RgbRed(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
-                      applyGainLUT(RgbGreen(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
-                      applyGainLUT(RgbBlue(), value, gainLUT));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
-                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
-                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
-                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
-                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
-                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
-    }
-  }
-
-  for (int boost = 1; boost <= 10; boost++) {
-    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
-                                       .minContentBoost = 1.0f };
-    GainLUT gainLUT(&metadata);
-    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
-                      applyGainLUT(RgbBlack(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
-                      applyGainLUT(RgbWhite(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
-                      applyGainLUT(RgbRed(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
-                      applyGainLUT(RgbGreen(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
-                      applyGainLUT(RgbBlue(), value, gainLUT));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
-                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
-                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
-                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
-                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
-                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
-    }
-  }
-
-  for (int boost = 1; boost <= 10; boost++) {
-    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
-                                       .minContentBoost = 1.0f / pow(static_cast<float>(boost),
-                                                              1.0f / 3.0f) };
-    GainLUT gainLUT(&metadata);
-    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
-                      applyGainLUT(RgbBlack(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
-                      applyGainLUT(RgbWhite(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
-                      applyGainLUT(RgbRed(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
-                      applyGainLUT(RgbGreen(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
-                      applyGainLUT(RgbBlue(), value, gainLUT));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
-                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
-                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
-                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
-                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
-                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) {
-  EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
-  EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
-  EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon());
-  EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f);
-}
-
-TEST_F(GainMapMathTest, ColorConversionLookup) {
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709),
-            identityConversion);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3),
-            p3ToBt709);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100),
-            bt2100ToBt709);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709),
-            bt709ToP3);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3),
-            identityConversion);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100),
-            bt2100ToP3);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709),
-            bt709ToBt2100);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3),
-            p3ToBt2100);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100),
-            identityConversion);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100),
-            nullptr);
-}
-
-TEST_F(GainMapMathTest, EncodeGain) {
-  ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
-                                        .minContentBoost = 1.0f / 4.0f };
-
-  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127);
-  EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127);
-  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(0.5f, 0.0f, &metadata), 0);
-
-  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 127);
-  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(1.0f, 5.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(4.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(4.0f, 0.5f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 191);
-  EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 63);
-
-  metadata.maxContentBoost = 2.0f;
-  metadata.minContentBoost = 1.0f / 2.0f;
-
-  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 1.41421f, &metadata), 191);
-  EXPECT_EQ(encodeGain(1.41421f, 1.0f, &metadata), 63);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f / 8.0f;
-
-  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(8.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 2.82843f, &metadata), 191);
-  EXPECT_EQ(encodeGain(2.82843f, 1.0f, &metadata), 63);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f;
-
-  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
-
-  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 170);
-  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 85);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 0.5f;
-
-  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 63);
-  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
-
-  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 63);
-  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 191);
-  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 127);
-  EXPECT_EQ(encodeGain(1.0f, 0.7071f, &metadata), 31);
-  EXPECT_EQ(encodeGain(1.0f, 0.5f, &metadata), 0);
-}
-
-TEST_F(GainMapMathTest, ApplyGain) {
-  ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
-                                        .minContentBoost = 1.0f / 4.0f };
-  float displayBoost = metadata.maxContentBoost;
-
-  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack());
-  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack());
-  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 1.0f, &metadata), RgbBlack());
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
-
-  metadata.maxContentBoost = 2.0f;
-  metadata.minContentBoost = 1.0f / 2.0f;
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f / 8.0f;
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f;
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 0.5f;
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
-
-  Color e = {{{ 0.0f, 0.5f, 1.0f }}};
-  metadata.maxContentBoost = 4.0f;
-  metadata.minContentBoost = 1.0f / 4.0f;
-
-  EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f);
-  EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f);
-  EXPECT_RGB_NEAR(applyGain(e, 0.5f, &metadata), e);
-  EXPECT_RGB_NEAR(applyGain(e, 0.75f, &metadata), e * 2.0f);
-  EXPECT_RGB_NEAR(applyGain(e, 1.0f, &metadata), e * 4.0f);
-
-  EXPECT_RGB_EQ(applyGain(RgbBlack(), 1.0f, &metadata),
-                applyGain(RgbBlack(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(RgbWhite(), 1.0f, &metadata),
-                applyGain(RgbWhite(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(RgbRed(), 1.0f, &metadata),
-                applyGain(RgbRed(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(RgbGreen(), 1.0f, &metadata),
-                applyGain(RgbGreen(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(RgbBlue(), 1.0f, &metadata),
-                applyGain(RgbBlue(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata),
-                applyGain(e, 1.0f, &metadata, displayBoost));
-}
-
-TEST_F(GainMapMathTest, GetYuv420Pixel) {
-  jpegr_uncompressed_struct image = Yuv420Image();
-  Color (*colors)[4] = Yuv420Colors();
-
-  for (size_t y = 0; y < 4; ++y) {
-    for (size_t x = 0; x < 4; ++x) {
-      EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, GetP010Pixel) {
-  jpegr_uncompressed_struct image = P010Image();
-  Color (*colors)[4] = P010Colors();
-
-  for (size_t y = 0; y < 4; ++y) {
-    for (size_t x = 0; x < 4; ++x) {
-      EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleYuv420) {
-  jpegr_uncompressed_struct image = Yuv420Image();
-  Color (*colors)[4] = Yuv420Colors();
-
-  static const size_t kMapScaleFactor = 2;
-  for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
-      Color min = {{{ 1.0f, 1.0f, 1.0f }}};
-      Color max = {{{ -1.0f, -1.0f, -1.0f }}};
-
-      for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
-        for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
-          Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
-          min = ColorMin(min, e);
-          max = ColorMax(max, e);
-        }
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleP010) {
-  jpegr_uncompressed_struct image = P010Image();
-  Color (*colors)[4] = P010Colors();
-
-  static const size_t kMapScaleFactor = 2;
-  for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
-      Color min = {{{ 1.0f, 1.0f, 1.0f }}};
-      Color max = {{{ -1.0f, -1.0f, -1.0f }}};
-
-      for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
-        for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
-          Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
-          min = ColorMin(min, e);
-          max = ColorMax(max, e);
-        }
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleMap) {
-  jpegr_uncompressed_struct image = MapImage();
-  float (*values)[4] = MapValues();
-
-  static const size_t kMapScaleFactor = 2;
-  ShepardsIDW idwTable(kMapScaleFactor);
-  for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) {
-      size_t x_base = x / kMapScaleFactor;
-      size_t y_base = y / kMapScaleFactor;
-
-      float min = 1.0f;
-      float max = -1.0f;
-
-      min = fmin(min, values[y_base][x_base]);
-      max = fmax(max, values[y_base][x_base]);
-      if (y_base + 1 < 4) {
-        min = fmin(min, values[y_base + 1][x_base]);
-        max = fmax(max, values[y_base + 1][x_base]);
-      }
-      if (x_base + 1 < 4) {
-        min = fmin(min, values[y_base][x_base + 1]);
-        max = fmax(max, values[y_base][x_base + 1]);
-      }
-      if (y_base + 1 < 4 && x_base + 1 < 4) {
-        min = fmin(min, values[y_base + 1][x_base + 1]);
-        max = fmax(max, values[y_base + 1][x_base + 1]);
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
-                  testing::AllOf(testing::Ge(min), testing::Le(max)));
-      EXPECT_EQ(sampleMap(&image, kMapScaleFactor, x, y, idwTable),
-                sampleMap(&image, kMapScaleFactor, x, y));
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, ColorToRgba1010102) {
-  EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30);
-  EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF);
-  EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff);
-  EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10);
-  EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20);
-
-  Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
-  EXPECT_EQ(colorToRgba1010102(e_gamma),
-            0x3 << 30
-          | static_cast<uint32_t>(0.1f * static_cast<float>(0x3ff))
-          | static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10
-          | static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20);
-}
-
-TEST_F(GainMapMathTest, ColorToRgbaF16) {
-  EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48);
-  EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00);
-  EXPECT_EQ(colorToRgbaF16(RgbRed()),   (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00));
-  EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 16));
-  EXPECT_EQ(colorToRgbaF16(RgbBlue()),  (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 32));
-
-  Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
-  EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66);
-}
-
-TEST_F(GainMapMathTest, Float32ToFloat16) {
-  EXPECT_EQ(floatToHalf(0.1f), 0x2E66);
-  EXPECT_EQ(floatToHalf(0.0f), 0x0);
-  EXPECT_EQ(floatToHalf(1.0f), 0x3C00);
-  EXPECT_EQ(floatToHalf(-1.0f), 0xBC00);
-  EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF);  // float max
-  EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF);  // float min
-  EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0);  // float zero
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) {
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance),
-                  0.0f);
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance),
-              srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance),
-              srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance),
-              srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) {
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance),
-                  0.0f);
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance),
-              p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance),
-              p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance),
-              p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) {
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance),
-                  0.0f);
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance),
-              bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance),
-              bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance),
-              bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) {
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion,
-                                       bt2100Luminance, kHlgMaxNits),
-                  0.0f);
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion,
-                                       bt2100Luminance, kHlgMaxNits),
-                  kHlgMaxNits);
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminancePq) {
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-                  0.0f);
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-                  kPqMaxNits);
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, ApplyMap) {
-  ultrahdr_metadata_struct metadata = { .maxContentBoost = 8.0f,
-                                     .minContentBoost = 1.0f / 8.0f };
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
-                RgbWhite() * 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata),
-                  RgbRed() * 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata),
-                  RgbGreen() * 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata),
-                  RgbBlue() * 8.0f);
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata),
-                RgbWhite() * sqrt(8.0f));
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata),
-                  RgbRed() * sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata),
-                  RgbGreen() * sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata),
-                  RgbBlue() * sqrt(8.0f));
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
-                RgbWhite());
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata),
-                  RgbRed());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata),
-                  RgbGreen());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata),
-                  RgbBlue());
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
-                RgbWhite() / sqrt(8.0f));
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata),
-                  RgbRed() / sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata),
-                  RgbGreen() / sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata),
-                  RgbBlue() / sqrt(8.0f));
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite() / 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata),
-                  RgbRed() / 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata),
-                  RgbGreen() / 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata),
-                  RgbBlue() / 8.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f;
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
-                RgbWhite() * 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata),
-                RgbWhite() * 4.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata),
-                RgbWhite() * 2.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite());
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 0.5f;;
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
-                RgbWhite() * 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata),
-                RgbWhite() * 4.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
-                RgbWhite() * 2.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
-                RgbWhite());
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite() / 2.0f);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/icchelper_test.cpp b/libs/ultrahdr/tests/icchelper_test.cpp
deleted file mode 100644
index ff61c08..0000000
--- a/libs/ultrahdr/tests/icchelper_test.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/ultrahdr.h>
-#include <utils/Log.h>
-
-namespace android::ultrahdr {
-
-class IccHelperTest : public testing::Test {
-public:
-    IccHelperTest();
-    ~IccHelperTest();
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-};
-
-IccHelperTest::IccHelperTest() {}
-
-IccHelperTest::~IccHelperTest() {}
-
-void IccHelperTest::SetUp() {}
-
-void IccHelperTest::TearDown() {}
-
-TEST_F(IccHelperTest, iccWriteThenRead) {
-    sp<DataStruct> iccBt709 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                         ULTRAHDR_COLORGAMUT_BT709);
-    ASSERT_NE(iccBt709->getLength(), 0);
-    ASSERT_NE(iccBt709->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()),
-              ULTRAHDR_COLORGAMUT_BT709);
-
-    sp<DataStruct> iccP3 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3);
-    ASSERT_NE(iccP3->getLength(), 0);
-    ASSERT_NE(iccP3->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()),
-              ULTRAHDR_COLORGAMUT_P3);
-
-    sp<DataStruct> iccBt2100 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                          ULTRAHDR_COLORGAMUT_BT2100);
-    ASSERT_NE(iccBt2100->getLength(), 0);
-    ASSERT_NE(iccBt2100->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()),
-              ULTRAHDR_COLORGAMUT_BT2100);
-}
-
-TEST_F(IccHelperTest, iccEndianness) {
-    sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709);
-    size_t profile_size = icc->getLength() - kICCIdentifierSize;
-
-    uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc->getData()) + kICCIdentifierSize;
-    uint32_t encoded_size = static_cast<uint32_t>(icc_bytes[0]) << 24 |
-                            static_cast<uint32_t>(icc_bytes[1]) << 16 |
-                            static_cast<uint32_t>(icc_bytes[2]) << 8 |
-                            static_cast<uint32_t>(icc_bytes[3]);
-
-    EXPECT_EQ(static_cast<size_t>(encoded_size), profile_size);
-}
-
-}  // namespace android::ultrahdr
-
diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
deleted file mode 100644
index af0d59e..0000000
--- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/jpegdecoderhelper.h>
-#include <utils/Log.h>
-
-#include <fcntl.h>
-
-namespace android::ultrahdr {
-
-// No ICC or EXIF
-#define YUV_IMAGE "/data/local/tmp/minnie-320x240-yuv.jpg"
-#define YUV_IMAGE_SIZE 20193
-// Has ICC and EXIF
-#define YUV_ICC_IMAGE "/data/local/tmp/minnie-320x240-yuv-icc.jpg"
-#define YUV_ICC_IMAGE_SIZE 34266
-// No ICC or EXIF
-#define GREY_IMAGE "/data/local/tmp/minnie-320x240-y.jpg"
-#define GREY_IMAGE_SIZE 20193
-
-#define IMAGE_WIDTH 320
-#define IMAGE_HEIGHT 240
-
-class JpegDecoderHelperTest : public testing::Test {
-public:
-    struct Image {
-        std::unique_ptr<uint8_t[]> buffer;
-        size_t size;
-    };
-    JpegDecoderHelperTest();
-    ~JpegDecoderHelperTest();
-
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-
-    Image mYuvImage, mYuvIccImage, mGreyImage;
-};
-
-JpegDecoderHelperTest::JpegDecoderHelperTest() {}
-
-JpegDecoderHelperTest::~JpegDecoderHelperTest() {}
-
-static size_t getFileSize(int fd) {
-    struct stat st;
-    if (fstat(fd, &st) < 0) {
-        ALOGW("%s : fstat failed", __func__);
-        return 0;
-    }
-    return st.st_size; // bytes
-}
-
-static bool loadFile(const char filename[], JpegDecoderHelperTest::Image* result) {
-    int fd = open(filename, O_CLOEXEC);
-    if (fd < 0) {
-        return false;
-    }
-    int length = getFileSize(fd);
-    if (length == 0) {
-        close(fd);
-        return false;
-    }
-    result->buffer.reset(new uint8_t[length]);
-    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
-        close(fd);
-        return false;
-    }
-    close(fd);
-    return true;
-}
-
-void JpegDecoderHelperTest::SetUp() {
-    if (!loadFile(YUV_IMAGE, &mYuvImage)) {
-        FAIL() << "Load file " << YUV_IMAGE << " failed";
-    }
-    mYuvImage.size = YUV_IMAGE_SIZE;
-    if (!loadFile(YUV_ICC_IMAGE, &mYuvIccImage)) {
-        FAIL() << "Load file " << YUV_ICC_IMAGE << " failed";
-    }
-    mYuvIccImage.size = YUV_ICC_IMAGE_SIZE;
-    if (!loadFile(GREY_IMAGE, &mGreyImage)) {
-        FAIL() << "Load file " << GREY_IMAGE << " failed";
-    }
-    mGreyImage.size = GREY_IMAGE_SIZE;
-}
-
-void JpegDecoderHelperTest::TearDown() {}
-
-TEST_F(JpegDecoderHelperTest, decodeYuvImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-    EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()),
-              ULTRAHDR_COLORGAMUT_UNSPECIFIED);
-}
-
-TEST_F(JpegDecoderHelperTest, decodeYuvIccImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-    EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()),
-              ULTRAHDR_COLORGAMUT_BT709);
-}
-
-TEST_F(JpegDecoderHelperTest, decodeGreyImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) {
-    size_t width = 0, height = 0;
-    std::vector<uint8_t> icc, exif;
-
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width,
-                                                     &height, &icc, &exif));
-
-    EXPECT_EQ(width, IMAGE_WIDTH);
-    EXPECT_EQ(height, IMAGE_HEIGHT);
-    EXPECT_EQ(icc.size(), 0);
-    EXPECT_EQ(exif.size(), 0);
-}
-
-TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) {
-    size_t width = 0, height = 0;
-    std::vector<uint8_t> icc, exif;
-
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvIccImage.buffer.get(), mYuvIccImage.size,
-                                                     &width, &height, &icc, &exif));
-
-    EXPECT_EQ(width, IMAGE_WIDTH);
-    EXPECT_EQ(height, IMAGE_HEIGHT);
-    EXPECT_GT(icc.size(), 0);
-    EXPECT_GT(exif.size(), 0);
-
-    EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
deleted file mode 100644
index af54eb2..0000000
--- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <ultrahdr/jpegencoderhelper.h>
-#include <gtest/gtest.h>
-#include <utils/Log.h>
-
-#include <fcntl.h>
-
-namespace android::ultrahdr {
-
-#define ALIGNED_IMAGE "/data/local/tmp/minnie-320x240.yu12"
-#define ALIGNED_IMAGE_WIDTH 320
-#define ALIGNED_IMAGE_HEIGHT 240
-#define SINGLE_CHANNEL_IMAGE "/data/local/tmp/minnie-320x240.y"
-#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH
-#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT
-#define UNALIGNED_IMAGE "/data/local/tmp/minnie-318x240.yu12"
-#define UNALIGNED_IMAGE_WIDTH 318
-#define UNALIGNED_IMAGE_HEIGHT 240
-#define JPEG_QUALITY 90
-
-class JpegEncoderHelperTest : public testing::Test {
-public:
-    struct Image {
-        std::unique_ptr<uint8_t[]> buffer;
-        size_t width;
-        size_t height;
-    };
-    JpegEncoderHelperTest();
-    ~JpegEncoderHelperTest();
-
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-
-    Image mAlignedImage, mUnalignedImage, mSingleChannelImage;
-};
-
-JpegEncoderHelperTest::JpegEncoderHelperTest() {}
-
-JpegEncoderHelperTest::~JpegEncoderHelperTest() {}
-
-static size_t getFileSize(int fd) {
-    struct stat st;
-    if (fstat(fd, &st) < 0) {
-        ALOGW("%s : fstat failed", __func__);
-        return 0;
-    }
-    return st.st_size; // bytes
-}
-
-static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) {
-    int fd = open(filename, O_CLOEXEC);
-    if (fd < 0) {
-        return false;
-    }
-    int length = getFileSize(fd);
-    if (length == 0) {
-        close(fd);
-        return false;
-    }
-    result->buffer.reset(new uint8_t[length]);
-    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
-        close(fd);
-        return false;
-    }
-    close(fd);
-    return true;
-}
-
-void JpegEncoderHelperTest::SetUp() {
-    if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) {
-        FAIL() << "Load file " << ALIGNED_IMAGE << " failed";
-    }
-    mAlignedImage.width = ALIGNED_IMAGE_WIDTH;
-    mAlignedImage.height = ALIGNED_IMAGE_HEIGHT;
-    if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) {
-        FAIL() << "Load file " << UNALIGNED_IMAGE << " failed";
-    }
-    mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH;
-    mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT;
-    if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
-        FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
-    }
-    mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
-    mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
-}
-
-void JpegEncoderHelperTest::TearDown() {}
-
-TEST_F(JpegEncoderHelperTest, encodeAlignedImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(),
-                                      mAlignedImage.buffer.get() +
-                                              mAlignedImage.width * mAlignedImage.height,
-                                      mAlignedImage.width, mAlignedImage.height,
-                                      mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY,
-                                      NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(),
-                                      mUnalignedImage.buffer.get() +
-                                              mUnalignedImage.width * mUnalignedImage.height,
-                                      mUnalignedImage.width, mUnalignedImage.height,
-                                      mUnalignedImage.width, mUnalignedImage.width / 2,
-                                      JPEG_QUALITY, NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), nullptr,
-                                      mSingleChannelImage.width, mSingleChannelImage.height,
-                                      mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
deleted file mode 100644
index 5fa758e..0000000
--- a/libs/ultrahdr/tests/jpegr_test.cpp
+++ /dev/null
@@ -1,2035 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <sys/time.h>
-#include <fstream>
-#include <iostream>
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-
-#include <gtest/gtest.h>
-#include <utils/Log.h>
-
-//#define DUMP_OUTPUT
-
-namespace android::ultrahdr {
-
-// resources used by unit tests
-const char* kYCbCrP010FileName = "/data/local/tmp/raw_p010_image.p010";
-const char* kYCbCr420FileName = "/data/local/tmp/raw_yuv420_image.yuv420";
-const char* kSdrJpgFileName = "/data/local/tmp/jpeg_image.jpg";
-const int kImageWidth = 1280;
-const int kImageHeight = 720;
-const int kQuality = 90;
-
-// Wrapper to describe the input type
-typedef enum {
-  YCbCr_p010 = 0,
-  YCbCr_420 = 1,
-} UhdrInputFormat;
-
-/**
- * Wrapper class for raw resource
- * Sample usage:
- *   UhdrUnCompressedStructWrapper rawImg(width, height, YCbCr_p010);
- *   rawImg.setImageColorGamut(colorGamut));
- *   rawImg.setImageStride(strideLuma, strideChroma); // optional
- *   rawImg.setChromaMode(false); // optional
- *   rawImg.allocateMemory();
- *   rawImg.loadRawResource(kYCbCrP010FileName);
- */
-class UhdrUnCompressedStructWrapper {
-public:
-  UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height, UhdrInputFormat format);
-  ~UhdrUnCompressedStructWrapper() = default;
-
-  bool setChromaMode(bool isChromaContiguous);
-  bool setImageStride(int lumaStride, int chromaStride);
-  bool setImageColorGamut(ultrahdr_color_gamut colorGamut);
-  bool allocateMemory();
-  bool loadRawResource(const char* fileName);
-  jr_uncompressed_ptr getImageHandle();
-
-private:
-  std::unique_ptr<uint8_t[]> mLumaData;
-  std::unique_ptr<uint8_t[]> mChromaData;
-  jpegr_uncompressed_struct mImg;
-  UhdrInputFormat mFormat;
-  bool mIsChromaContiguous;
-};
-
-/**
- * Wrapper class for compressed resource
- * Sample usage:
- *   UhdrCompressedStructWrapper jpgImg(width, height);
- *   rawImg.allocateMemory();
- */
-class UhdrCompressedStructWrapper {
-public:
-  UhdrCompressedStructWrapper(uint32_t width, uint32_t height);
-  ~UhdrCompressedStructWrapper() = default;
-
-  bool allocateMemory();
-  jr_compressed_ptr getImageHandle();
-
-private:
-  std::unique_ptr<uint8_t[]> mData;
-  jpegr_compressed_struct mImg{};
-  uint32_t mWidth;
-  uint32_t mHeight;
-};
-
-UhdrUnCompressedStructWrapper::UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height,
-                                                             UhdrInputFormat format) {
-  mImg.data = nullptr;
-  mImg.width = width;
-  mImg.height = height;
-  mImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  mImg.chroma_data = nullptr;
-  mImg.luma_stride = 0;
-  mImg.chroma_stride = 0;
-  mFormat = format;
-  mIsChromaContiguous = true;
-}
-
-bool UhdrUnCompressedStructWrapper::setChromaMode(bool isChromaContiguous) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  mIsChromaContiguous = isChromaContiguous;
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::setImageStride(int lumaStride, int chromaStride) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  if (lumaStride != 0) {
-    if (lumaStride < mImg.width) {
-      std::cerr << "Bad luma stride received" << std::endl;
-      return false;
-    }
-    mImg.luma_stride = lumaStride;
-  }
-  if (chromaStride != 0) {
-    if (mFormat == YCbCr_p010 && chromaStride < mImg.width) {
-      std::cerr << "Bad chroma stride received for format YCbCrP010" << std::endl;
-      return false;
-    }
-    if (mFormat == YCbCr_420 && chromaStride < (mImg.width >> 1)) {
-      std::cerr << "Bad chroma stride received for format YCbCr420" << std::endl;
-      return false;
-    }
-    mImg.chroma_stride = chromaStride;
-  }
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::setImageColorGamut(ultrahdr_color_gamut colorGamut) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  mImg.colorGamut = colorGamut;
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::allocateMemory() {
-  if (mImg.width == 0 || (mImg.width % 2 != 0) || mImg.height == 0 || (mImg.height % 2 != 0) ||
-      (mFormat != YCbCr_p010 && mFormat != YCbCr_420)) {
-    std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-    return false;
-  }
-  int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
-  int lumaSize = lumaStride * mImg.height * (mFormat == YCbCr_p010 ? 2 : 1);
-  int chromaSize = (mImg.height >> 1) * (mFormat == YCbCr_p010 ? 2 : 1);
-  if (mIsChromaContiguous) {
-    chromaSize *= lumaStride;
-  } else {
-    if (mImg.chroma_stride == 0) {
-      std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-      return false;
-    }
-    if (mFormat == YCbCr_p010) {
-      chromaSize *= mImg.chroma_stride;
-    } else {
-      chromaSize *= (mImg.chroma_stride * 2);
-    }
-  }
-  if (mIsChromaContiguous) {
-    mLumaData = std::make_unique<uint8_t[]>(lumaSize + chromaSize);
-    mImg.data = mLumaData.get();
-    mImg.chroma_data = nullptr;
-  } else {
-    mLumaData = std::make_unique<uint8_t[]>(lumaSize);
-    mImg.data = mLumaData.get();
-    mChromaData = std::make_unique<uint8_t[]>(chromaSize);
-    mImg.chroma_data = mChromaData.get();
-  }
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) {
-  if (!mImg.data) {
-    std::cerr << "memory is not allocated, read not possible" << std::endl;
-    return false;
-  }
-  std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
-  if (ifd.good()) {
-    int bpp = mFormat == YCbCr_p010 ? 2 : 1;
-    int size = ifd.tellg();
-    int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling
-    if (size < length) {
-      std::cerr << "requested to read " << length << " bytes from file : " << fileName
-                << ", file contains only " << length << " bytes" << std::endl;
-      return false;
-    }
-    ifd.seekg(0, std::ios::beg);
-    int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
-    char* mem = static_cast<char*>(mImg.data);
-    for (int i = 0; i < mImg.height; i++) {
-      ifd.read(mem, mImg.width * bpp);
-      mem += lumaStride * bpp;
-    }
-    if (!mIsChromaContiguous) {
-      mem = static_cast<char*>(mImg.chroma_data);
-    }
-    int chromaStride;
-    if (mIsChromaContiguous) {
-      chromaStride = mFormat == YCbCr_p010 ? lumaStride : lumaStride / 2;
-    } else {
-      if (mFormat == YCbCr_p010) {
-        chromaStride = mImg.chroma_stride == 0 ? lumaStride : mImg.chroma_stride;
-      } else {
-        chromaStride = mImg.chroma_stride == 0 ? (lumaStride / 2) : mImg.chroma_stride;
-      }
-    }
-    if (mFormat == YCbCr_p010) {
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, mImg.width * 2);
-        mem += chromaStride * 2;
-      }
-    } else {
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, (mImg.width / 2));
-        mem += chromaStride;
-      }
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, (mImg.width / 2));
-        mem += chromaStride;
-      }
-    }
-    return true;
-  }
-  std::cerr << "unable to open file : " << fileName << std::endl;
-  return false;
-}
-
-jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() {
-  return &mImg;
-}
-
-UhdrCompressedStructWrapper::UhdrCompressedStructWrapper(uint32_t width, uint32_t height) {
-  mWidth = width;
-  mHeight = height;
-}
-
-bool UhdrCompressedStructWrapper::allocateMemory() {
-  if (mWidth == 0 || (mWidth % 2 != 0) || mHeight == 0 || (mHeight % 2 != 0)) {
-    std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-    return false;
-  }
-  int maxLength = std::max(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2));
-  mData = std::make_unique<uint8_t[]>(maxLength);
-  mImg.data = mData.get();
-  mImg.length = 0;
-  mImg.maxLength = maxLength;
-  return true;
-}
-
-jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() {
-  return &mImg;
-}
-
-static bool writeFile(const char* filename, void*& result, int length) {
-  std::ofstream ofd(filename, std::ios::binary);
-  if (ofd.is_open()) {
-    ofd.write(static_cast<char*>(result), length);
-    return true;
-  }
-  std::cerr << "unable to write to file : " << filename << std::endl;
-  return false;
-}
-
-static bool readFile(const char* fileName, void*& result, int maxLength, int& length) {
-  std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
-  if (ifd.good()) {
-    length = ifd.tellg();
-    if (length > maxLength) {
-      std::cerr << "not enough space to read file" << std::endl;
-      return false;
-    }
-    ifd.seekg(0, std::ios::beg);
-    ifd.read(static_cast<char*>(result), length);
-    return true;
-  }
-  std::cerr << "unable to read file : " << fileName << std::endl;
-  return false;
-}
-
-void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) {
-  std::vector<uint8_t> iccData(0);
-  std::vector<uint8_t> exifData(0);
-  jpegr_info_struct info{0, 0, &iccData, &exifData};
-  JpegR jpegHdr;
-  ASSERT_EQ(OK, jpegHdr.getJPEGRInfo(img, &info));
-  ASSERT_EQ(kImageWidth, info.width);
-  ASSERT_EQ(kImageHeight, info.height);
-  size_t outSize = info.width * info.height * 8;
-  std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
-  jpegr_uncompressed_struct destImage{};
-  destImage.data = data.get();
-  ASSERT_EQ(OK, jpegHdr.decodeJPEGR(img, &destImage));
-  ASSERT_EQ(kImageWidth, destImage.width);
-  ASSERT_EQ(kImageHeight, destImage.height);
-#ifdef DUMP_OUTPUT
-  if (!writeFile(outFileName, destImage.data, outSize)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-}
-
-// ============================================================================
-// Unit Tests
-// ============================================================================
-
-// Test Encode API-0 invalid arguments
-TEST(JpegRTest, EncodeAPI0WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), -1, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), 101, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
-                                  nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test p010 input
-  {
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1)));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-}
-
-/* Test Encode API-1 invalid arguments */
-TEST(JpegRTest, EncodeAPI1WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), -1, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), 101, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
-                                  nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test p010 input
-  {
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-
-  // test 420 input
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr 420 image";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr 420 image";
-  }
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth - 1;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = 0;
-    rawImg420->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = 0;
-    rawImg420->chroma_data = rawImgP010->data;
-    rawImg420->chroma_stride = kWidth / 2 - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride for 420";
-  }
-}
-
-/* Test Encode API-2 invalid arguments */
-TEST(JpegRTest, EncodeAPI2WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test compressed image
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-  }
-
-  // test p010 input
-  {
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-
-  // test 420 input
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr 420 image";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr 420 image";
-  }
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth - 1;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = 0;
-    rawImg420->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = 0;
-    rawImg420->chroma_data = rawImgP010->data;
-    rawImg420->chroma_stride = kWidth / 2 - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride for 420";
-  }
-}
-
-/* Test Encode API-3 invalid arguments */
-TEST(JpegRTest, EncodeAPI3WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test compressed image
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-  }
-
-  // test p010 input
-  {
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-}
-
-/* Test Encode API-4 invalid arguments */
-TEST(JpegRTest, EncodeAPI4WithInvalidArgs) {
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgImg2(16, 16);
-  JpegR uHdrLib;
-
-  // test dest
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, nullptr),
-            OK)
-          << "fail, API allows nullptr dest";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr,
-                                jpgImg2.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr dest";
-
-  // test primary image
-  ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr primary image";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg2.getImageHandle(), jpgImg.getImageHandle(), nullptr,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr primary image";
-
-  // test gain map
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), nullptr, nullptr, jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr gain map image";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg2.getImageHandle(), nullptr,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr gain map image";
-
-  // test metadata
-  ultrahdr_metadata_struct good_metadata;
-  good_metadata.version = "1.0";
-  good_metadata.minContentBoost = 1.0f;
-  good_metadata.maxContentBoost = 2.0f;
-  good_metadata.gamma = 1.0f;
-  good_metadata.offsetSdr = 0.0f;
-  good_metadata.offsetHdr = 0.0f;
-  good_metadata.hdrCapacityMin = 1.0f;
-  good_metadata.hdrCapacityMax = 2.0f;
-
-  ultrahdr_metadata_struct metadata = good_metadata;
-  metadata.version = "1.1";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata version";
-
-  metadata = good_metadata;
-  metadata.minContentBoost = 3.0f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata content boost";
-
-  metadata = good_metadata;
-  metadata.gamma = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata gamma";
-
-  metadata = good_metadata;
-  metadata.offsetSdr = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata offset sdr";
-
-  metadata = good_metadata;
-  metadata.offsetHdr = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata offset hdr";
-
-  metadata = good_metadata;
-  metadata.hdrCapacityMax = 0.5f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata hdr capacity max";
-
-  metadata = good_metadata;
-  metadata.hdrCapacityMin = 0.5f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata hdr capacity min";
-}
-
-/* Test Decode API invalid arguments */
-TEST(JpegRTest, DecodeAPIWithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  jpegr_uncompressed_struct destImage{};
-  size_t outSize = 16 * 16 * 8;
-  std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
-  destImage.data = data.get();
-
-  // test jpegr image
-  ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), OK)
-          << "fail, API allows nullptr for jpegr img";
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
-          << "fail, API allows nullptr for jpegr img";
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test dest image
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), OK)
-          << "fail, API allows nullptr for dest";
-  destImage.data = nullptr;
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
-          << "fail, API allows nullptr for dest";
-  destImage.data = data.get();
-
-  // test max display boost
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), OK)
-          << "fail, API allows invalid max display boost";
-
-  // test output format
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
-                                static_cast<ultrahdr_output_format>(-1)),
-            OK)
-          << "fail, API allows invalid output format";
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
-                                static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)),
-            OK)
-          << "fail, API allows invalid output format";
-}
-
-TEST(JpegRTest, writeXmpThenRead) {
-  ultrahdr_metadata_struct metadata_expected;
-  metadata_expected.version = "1.0";
-  metadata_expected.maxContentBoost = 1.25f;
-  metadata_expected.minContentBoost = 0.75f;
-  metadata_expected.gamma = 1.0f;
-  metadata_expected.offsetSdr = 0.0f;
-  metadata_expected.offsetHdr = 0.0f;
-  metadata_expected.hdrCapacityMin = 1.0f;
-  metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost;
-  const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-  const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
-
-  std::string xmp = generateXmpForSecondaryImage(metadata_expected);
-
-  std::vector<uint8_t> xmpData;
-  xmpData.reserve(nameSpaceLength + xmp.size());
-  xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(nameSpace.c_str()),
-                 reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
-  xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
-                 reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
-
-  ultrahdr_metadata_struct metadata_read;
-  EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
-  EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
-  EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost);
-  EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma);
-  EXPECT_FLOAT_EQ(metadata_expected.offsetSdr, metadata_read.offsetSdr);
-  EXPECT_FLOAT_EQ(metadata_expected.offsetHdr, metadata_read.offsetHdr);
-  EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMin, metadata_read.hdrCapacityMin);
-  EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax);
-}
-
-class JpegRAPIEncodeAndDecodeTest
-      : public ::testing::TestWithParam<std::tuple<ultrahdr_color_gamut, ultrahdr_color_gamut>> {
-public:
-  JpegRAPIEncodeAndDecodeTest()
-        : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){};
-
-  const ultrahdr_color_gamut mP010ColorGamut;
-  const ultrahdr_color_gamut mYuv420ColorGamut;
-};
-
-/* Test Encode API-0 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) {
-  // reference encode
-  UhdrUnCompressedStructWrapper rawImg(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImg.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImg.allocateMemory());
-  ASSERT_TRUE(rawImg.loadRawResource(kYCbCrP010FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle(), kQuality, nullptr),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, 0));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, kImageWidth + 28));
-    ASSERT_TRUE(rawImg2.setChromaMode(false));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(0, kImageWidth + 34));
-    ASSERT_TRUE(rawImg2.setChromaMode(false));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth, kImageWidth + 38));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api0_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api0_output.rgb"));
-}
-
-/* Test Encode API-1 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(),
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle(), kQuality, nullptr),
-            OK);
-  // encode with luma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 64, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 14, 0));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 46, kImageWidth / 2 + 34));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth / 2 + 38));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 26, kImageWidth / 2 + 44));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api1_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api1_output.rgb"));
-}
-
-/* Test Encode API-2 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgSdr.allocateMemory());
-  auto sdr = jpgSdr.getImageHandle();
-  ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle()),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api2_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api2_output.rgb"));
-}
-
-/* Test Encode API-3 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgSdr.allocateMemory());
-  auto sdr = jpgSdr.getImageHandle();
-  ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr,
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle()),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set and no chroma ptr
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 32, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api3_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api3_output.rgb"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-        JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest,
-        ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
-                                             ULTRAHDR_COLORGAMUT_BT2100),
-                           ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
-                                             ULTRAHDR_COLORGAMUT_BT2100)));
-
-// ============================================================================
-// Profiling
-// ============================================================================
-
-class Profiler {
-public:
-  void timerStart() { gettimeofday(&mStartingTime, nullptr); }
-
-  void timerStop() { gettimeofday(&mEndingTime, nullptr); }
-
-  int64_t elapsedTime() {
-    struct timeval elapsedMicroseconds;
-    elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec;
-    elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec;
-    return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec;
-  }
-
-private:
-  struct timeval mStartingTime;
-  struct timeval mEndingTime;
-};
-
-class JpegRBenchmark : public JpegR {
-public:
-  void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
-                                ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map);
-  void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
-                             ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest);
-
-private:
-  const int kProfileCount = 10;
-};
-
-void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
-                                              jr_uncompressed_ptr p010Image,
-                                              ultrahdr_metadata_ptr metadata,
-                                              jr_uncompressed_ptr map) {
-  ASSERT_EQ(yuv420Image->width, p010Image->width);
-  ASSERT_EQ(yuv420Image->height, p010Image->height);
-  Profiler profileGenerateMap;
-  profileGenerateMap.timerStart();
-  for (auto i = 0; i < kProfileCount; i++) {
-    ASSERT_EQ(OK,
-              generateGainMap(yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                              metadata, map));
-    if (i != kProfileCount - 1) delete[] static_cast<uint8_t*>(map->data);
-  }
-  profileGenerateMap.timerStop();
-  ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
-        profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f));
-}
-
-void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
-                                           ultrahdr_metadata_ptr metadata,
-                                           jr_uncompressed_ptr dest) {
-  Profiler profileRecMap;
-  profileRecMap.timerStart();
-  for (auto i = 0; i < kProfileCount; i++) {
-    ASSERT_EQ(OK,
-              applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
-                           metadata->maxContentBoost /* displayBoost */, dest));
-  }
-  profileRecMap.timerStop();
-  ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
-        profileRecMap.elapsedTime() / (kProfileCount * 1000.f));
-}
-
-TEST(JpegRTest, ProfileGainMapFuncs) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  ultrahdr_metadata_struct metadata = {.version = "1.0"};
-  jpegr_uncompressed_struct map = {.data = NULL,
-                                   .width = 0,
-                                   .height = 0,
-                                   .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-  {
-    auto rawImg = rawImgP010.getImageHandle();
-    if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
-    if (!rawImg->chroma_data) {
-      uint16_t* data = reinterpret_cast<uint16_t*>(rawImg->data);
-      rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
-      rawImg->chroma_stride = rawImg->luma_stride;
-    }
-  }
-  {
-    auto rawImg = rawImg420.getImageHandle();
-    if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
-    if (!rawImg->chroma_data) {
-      uint8_t* data = reinterpret_cast<uint8_t*>(rawImg->data);
-      rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
-      rawImg->chroma_stride = rawImg->luma_stride / 2;
-    }
-  }
-
-  JpegRBenchmark benchmark;
-  ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap(rawImg420.getImageHandle(),
-                                                             rawImgP010.getImageHandle(), &metadata,
-                                                             &map));
-
-  const int dstSize = kImageWidth * kImageWidth * 4;
-  auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
-  jpegr_uncompressed_struct dest = {.data = bufferDst.get(),
-                                    .width = 0,
-                                    .height = 0,
-                                    .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  ASSERT_NO_FATAL_FAILURE(
-          benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest));
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/vibrator/ExternalVibration.cpp b/libs/vibrator/ExternalVibration.cpp
index 80e911c..c97e496 100644
--- a/libs/vibrator/ExternalVibration.cpp
+++ b/libs/vibrator/ExternalVibration.cpp
@@ -17,7 +17,7 @@
 #include <vibrator/ExternalVibration.h>
 #include <vibrator/ExternalVibrationUtils.h>
 
-#include <android/os/IExternalVibratorService.h>
+#include <android/os/ExternalVibrationScale.h>
 #include <binder/Parcel.h>
 #include <log/log.h>
 #include <utils/Errors.h>
@@ -65,24 +65,36 @@
     return mToken == rhs.mToken;
 }
 
-os::HapticScale ExternalVibration::externalVibrationScaleToHapticScale(int externalVibrationScale) {
-    switch (externalVibrationScale) {
-        case IExternalVibratorService::SCALE_MUTE:
-            return os::HapticScale::MUTE;
-        case IExternalVibratorService::SCALE_VERY_LOW:
-            return os::HapticScale::VERY_LOW;
-        case IExternalVibratorService::SCALE_LOW:
-            return os::HapticScale::LOW;
-        case IExternalVibratorService::SCALE_NONE:
-            return os::HapticScale::NONE;
-        case IExternalVibratorService::SCALE_HIGH:
-            return os::HapticScale::HIGH;
-        case IExternalVibratorService::SCALE_VERY_HIGH:
-            return os::HapticScale::VERY_HIGH;
+os::HapticScale ExternalVibration::externalVibrationScaleToHapticScale(
+        os::ExternalVibrationScale externalVibrationScale) {
+    os::HapticLevel scaleLevel = os::HapticLevel::NONE;
+
+    switch (externalVibrationScale.scaleLevel) {
+        case os::ExternalVibrationScale::ScaleLevel::SCALE_MUTE:
+            scaleLevel = os::HapticLevel::MUTE;
+            break;
+        case os::ExternalVibrationScale::ScaleLevel::SCALE_VERY_LOW:
+            scaleLevel = os::HapticLevel::VERY_LOW;
+            break;
+        case os::ExternalVibrationScale::ScaleLevel::SCALE_LOW:
+            scaleLevel = os::HapticLevel::LOW;
+            break;
+        case os::ExternalVibrationScale::ScaleLevel::SCALE_NONE:
+            scaleLevel = os::HapticLevel::NONE;
+            break;
+        case os::ExternalVibrationScale::ScaleLevel::SCALE_HIGH:
+            scaleLevel = os::HapticLevel::HIGH;
+            break;
+        case os::ExternalVibrationScale::ScaleLevel::SCALE_VERY_HIGH:
+            scaleLevel = os::HapticLevel::VERY_HIGH;
+            break;
         default:
-          ALOGE("Unknown ExternalVibrationScale %d, not applying scaling", externalVibrationScale);
-          return os::HapticScale::NONE;
-      }
+            ALOGE("Unknown ExternalVibrationScale %d, not applying scaling",
+                  externalVibrationScale.scaleLevel);
+    }
+
+    return {/*level=*/scaleLevel, /*adaptiveScaleFactor=*/
+                      externalVibrationScale.adaptiveHapticsScale};
 }
 
 } // namespace os
diff --git a/libs/vibrator/ExternalVibrationUtils.cpp b/libs/vibrator/ExternalVibrationUtils.cpp
index 980b08b..761ac1b 100644
--- a/libs/vibrator/ExternalVibrationUtils.cpp
+++ b/libs/vibrator/ExternalVibrationUtils.cpp
@@ -26,30 +26,30 @@
 static constexpr float HAPTIC_SCALE_LOW_RATIO = 3.0f / 4.0f;
 static constexpr float HAPTIC_MAX_AMPLITUDE_FLOAT = 1.0f;
 
-float getHapticScaleGamma(HapticScale scale) {
-    switch (scale) {
-    case HapticScale::VERY_LOW:
+float getHapticScaleGamma(HapticLevel level) {
+    switch (level) {
+    case HapticLevel::VERY_LOW:
         return 2.0f;
-    case HapticScale::LOW:
+    case HapticLevel::LOW:
         return 1.5f;
-    case HapticScale::HIGH:
+    case HapticLevel::HIGH:
         return 0.5f;
-    case HapticScale::VERY_HIGH:
+    case HapticLevel::VERY_HIGH:
         return 0.25f;
     default:
         return 1.0f;
     }
 }
 
-float getHapticMaxAmplitudeRatio(HapticScale scale) {
-    switch (scale) {
-    case HapticScale::VERY_LOW:
+float getHapticMaxAmplitudeRatio(HapticLevel level) {
+    switch (level) {
+    case HapticLevel::VERY_LOW:
         return HAPTIC_SCALE_VERY_LOW_RATIO;
-    case HapticScale::LOW:
+    case HapticLevel::LOW:
         return HAPTIC_SCALE_LOW_RATIO;
-    case HapticScale::NONE:
-    case HapticScale::HIGH:
-    case HapticScale::VERY_HIGH:
+    case HapticLevel::NONE:
+    case HapticLevel::HIGH:
+    case HapticLevel::VERY_HIGH:
         return 1.0f;
     default:
         return 0.0f;
@@ -57,19 +57,28 @@
 }
 
 void applyHapticScale(float* buffer, size_t length, HapticScale scale) {
-    if (scale == HapticScale::MUTE) {
+    if (scale.isScaleMute()) {
         memset(buffer, 0, length * sizeof(float));
         return;
     }
-    if (scale == HapticScale::NONE) {
+    if (scale.isScaleNone()) {
         return;
     }
-    float gamma = getHapticScaleGamma(scale);
-    float maxAmplitudeRatio = getHapticMaxAmplitudeRatio(scale);
+    HapticLevel hapticLevel = scale.getLevel();
+    float adaptiveScaleFactor = scale.getAdaptiveScaleFactor();
+    float gamma = getHapticScaleGamma(hapticLevel);
+    float maxAmplitudeRatio = getHapticMaxAmplitudeRatio(hapticLevel);
+
     for (size_t i = 0; i < length; i++) {
-        float sign = buffer[i] >= 0 ? 1.0 : -1.0;
-        buffer[i] = powf(fabsf(buffer[i] / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
-                * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
+        if (hapticLevel != HapticLevel::NONE) {
+            float sign = buffer[i] >= 0 ? 1.0 : -1.0;
+            buffer[i] = powf(fabsf(buffer[i] / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
+                        * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
+        }
+
+        if (adaptiveScaleFactor != 1.0f) {
+            buffer[i] *= adaptiveScaleFactor;
+        }
     }
 }
 
@@ -89,13 +98,13 @@
 } // namespace
 
 bool isValidHapticScale(HapticScale scale) {
-    switch (scale) {
-    case HapticScale::MUTE:
-    case HapticScale::VERY_LOW:
-    case HapticScale::LOW:
-    case HapticScale::NONE:
-    case HapticScale::HIGH:
-    case HapticScale::VERY_HIGH:
+    switch (scale.getLevel()) {
+    case HapticLevel::MUTE:
+    case HapticLevel::VERY_LOW:
+    case HapticLevel::LOW:
+    case HapticLevel::NONE:
+    case HapticLevel::HIGH:
+    case HapticLevel::VERY_HIGH:
         return true;
     }
     return false;
diff --git a/libs/vibrator/include/vibrator/ExternalVibration.h b/libs/vibrator/include/vibrator/ExternalVibration.h
index 00cd3cd..ac2767e 100644
--- a/libs/vibrator/include/vibrator/ExternalVibration.h
+++ b/libs/vibrator/include/vibrator/ExternalVibration.h
@@ -24,6 +24,7 @@
 #include <system/audio.h>
 #include <utils/RefBase.h>
 #include <vibrator/ExternalVibrationUtils.h>
+#include <android/os/ExternalVibrationScale.h>
 
 namespace android {
 namespace os {
@@ -45,10 +46,11 @@
     audio_attributes_t getAudioAttributes() const { return mAttrs; }
     sp<IExternalVibrationController> getController() { return mController; }
 
-    /* Converts the scale from non-public ExternalVibrationService into the HapticScale
+    /* Converts the scale from non-public ExternalVibrationService into the HapticScaleLevel
      * used by the utils.
      */
-    static os::HapticScale externalVibrationScaleToHapticScale(int externalVibrationScale);
+    static os::HapticScale externalVibrationScaleToHapticScale(
+            os::ExternalVibrationScale externalVibrationScale);
 
 private:
     int32_t mUid;
diff --git a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
index ca219d3..d9a2b81 100644
--- a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
+++ b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
@@ -19,7 +19,7 @@
 
 namespace android::os {
 
-enum class HapticScale {
+enum class HapticLevel {
     MUTE = -100,
     VERY_LOW = -2,
     LOW = -1,
@@ -28,10 +28,41 @@
     VERY_HIGH = 2,
 };
 
+class HapticScale {
+private:
+HapticLevel mLevel = HapticLevel::NONE;
+float mAdaptiveScaleFactor = 1.0f;
+
+public:
+constexpr HapticScale(HapticLevel level, float adaptiveScaleFactor)
+    : mLevel(level), mAdaptiveScaleFactor(adaptiveScaleFactor) {}
+constexpr HapticScale(HapticLevel level) : mLevel(level) {}
+constexpr HapticScale() {}
+
+HapticLevel getLevel() const { return mLevel; }
+float getAdaptiveScaleFactor() const { return mAdaptiveScaleFactor; }
+
+bool operator==(const HapticScale& other) const {
+    return mLevel == other.mLevel && mAdaptiveScaleFactor == other.mAdaptiveScaleFactor;
+}
+
+bool isScaleNone() const {
+    return mLevel == HapticLevel::NONE && mAdaptiveScaleFactor == 1.0f;
+}
+
+bool isScaleMute() const {
+    return mLevel == HapticLevel::MUTE;
+}
+
+static HapticScale mute() {
+    return {/*level=*/os::HapticLevel::MUTE};
+}
+};
+
 bool isValidHapticScale(HapticScale scale);
 
-/* Scales the haptic data in given buffer using the selected HapticScale and ensuring no absolute
- * value will be larger than the absolute of given limit.
+/* Scales the haptic data in given buffer using the selected HapticScaleLevel and ensuring no
+ * absolute value will be larger than the absolute of given limit.
  * The limit will be ignored if it is NaN or zero.
  */
 void scaleHapticData(float* buffer, size_t length, HapticScale scale, float limit);
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index af0bcff..ec7b190 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -184,6 +184,14 @@
         }
     }
 
+    // Return true if app requests to use ANGLE, but ANGLE is not loaded.
+    // Difference with the case above is on devices that don't have an ANGLE apk installed,
+    // ANGLE namespace is not set. In that case if ANGLE in system partition is not loaded,
+    // we should unload the system driver first, and then load ANGLE from system partition.
+    if (!cnx->angleLoaded && android::GraphicsEnv::getInstance().shouldUseAngle()) {
+        return true;
+    }
+
     // Return true if native GLES drivers should be used and ANGLE is already loaded.
     if (android::GraphicsEnv::getInstance().shouldUseNativeDriver() && cnx->angleLoaded) {
         return true;
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index e5b1e44..ca9fe5e 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -23,10 +23,11 @@
     name: "libgpuservice_defaults",
     defaults: [
         "gpuservice_defaults",
-        "libvkjson_deps",
         "libgfxstats_deps",
         "libgpumem_deps",
         "libgpumemtracer_deps",
+        "libvkjson_deps",
+        "libvkprofiles_deps",
     ],
     cflags: [
         "-DLOG_TAG=\"GpuService\"",
@@ -46,6 +47,7 @@
         "libgpumemtracer",
         "libserviceutils",
         "libvkjson",
+        "libvkprofiles",
     ],
     export_static_lib_headers: [
         "libserviceutils",
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index 080e62b..fadb1fd 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -33,6 +33,7 @@
 #include <utils/String8.h>
 #include <utils/Trace.h>
 #include <vkjson.h>
+#include <vkprofiles.h>
 
 #include <thread>
 #include <memory>
@@ -44,6 +45,7 @@
 namespace {
 status_t cmdHelp(int out);
 status_t cmdVkjson(int out, int err);
+status_t cmdVkprofiles(int out, int err);
 void dumpGameDriverInfo(std::string* result);
 } // namespace
 
@@ -98,6 +100,12 @@
     mGpuStats->insertTargetStatsArray(appPackageName, driverVersionCode, stats, values, valueCount);
 }
 
+void GpuService::addVulkanEngineName(const std::string& appPackageName,
+                                     const uint64_t driverVersionCode,
+                                     const char* engineName) {
+    mGpuStats->addVulkanEngineName(appPackageName, driverVersionCode, engineName);
+}
+
 void GpuService::toggleAngleAsSystemDriver(bool enabled) {
     IPCThreadState* ipc = IPCThreadState::self();
     const int pid = ipc->getCallingPid();
@@ -150,6 +158,7 @@
 
     if (args.size() >= 1) {
         if (args[0] == String16("vkjson")) return cmdVkjson(out, err);
+        if (args[0] == String16("vkprofiles")) return cmdVkprofiles(out, err);
         if (args[0] == String16("help")) return cmdHelp(out);
     }
     // no command, or unrecognized command
@@ -216,31 +225,24 @@
 status_t cmdHelp(int out) {
     FILE* outs = fdopen(out, "w");
     if (!outs) {
-        ALOGE("vkjson: failed to create out stream: %s (%d)", strerror(errno), errno);
+        ALOGE("gpuservice: failed to create out stream: %s (%d)", strerror(errno), errno);
         return BAD_VALUE;
     }
     fprintf(outs,
             "GPU Service commands:\n"
-            "  vkjson   dump Vulkan properties as JSON\n");
+            "  vkjson      dump Vulkan properties as JSON\n"
+            "  vkprofiles  print support for select Vulkan profiles\n");
     fclose(outs);
     return NO_ERROR;
 }
 
-void vkjsonPrint(FILE* out) {
-    std::string json = VkJsonInstanceToJson(VkJsonGetInstance());
-    fwrite(json.data(), 1, json.size(), out);
-    fputc('\n', out);
+status_t cmdVkjson(int out, int /*err*/) {
+    dprintf(out, "%s\n", VkJsonInstanceToJson(VkJsonGetInstance()).c_str());
+    return NO_ERROR;
 }
 
-status_t cmdVkjson(int out, int /*err*/) {
-    FILE* outs = fdopen(out, "w");
-    if (!outs) {
-        int errnum = errno;
-        ALOGE("vkjson: failed to create output stream: %s", strerror(errnum));
-        return -errnum;
-    }
-    vkjsonPrint(outs);
-    fclose(outs);
+status_t cmdVkprofiles(int out, int /*err*/) {
+    dprintf(out, "%s\n", android::vkprofiles::vkProfiles().c_str());
     return NO_ERROR;
 }
 
diff --git a/services/gpuservice/gpustats/GpuStats.cpp b/services/gpuservice/gpustats/GpuStats.cpp
index 11b636d..6d758bc 100644
--- a/services/gpuservice/gpustats/GpuStats.cpp
+++ b/services/gpuservice/gpustats/GpuStats.cpp
@@ -181,6 +181,33 @@
     return insertTargetStatsArray(appPackageName, driverVersionCode, stats, &value, 1);
 }
 
+void GpuStats::addVulkanEngineName(const std::string& appPackageName,
+                                   const uint64_t driverVersionCode,
+                                   const char* engineNameCStr) {
+    ATRACE_CALL();
+
+    const std::string appStatsKey = appPackageName + std::to_string(driverVersionCode);
+    const size_t engineNameLen = std::min(strlen(engineNameCStr),
+                                          GpuStatsAppInfo::MAX_VULKAN_ENGINE_NAME_LENGTH);
+    const std::string engineName{engineNameCStr, engineNameLen};
+
+    std::lock_guard<std::mutex> lock(mLock);
+    registerStatsdCallbacksIfNeeded();
+
+    const auto foundApp = mAppStats.find(appStatsKey);
+    if (foundApp == mAppStats.end()) {
+        return;
+    }
+
+    // Storing in std::set<> is not efficient for serialization tasks. Use
+    // vector instead and filter out dups
+    std::vector<std::string>& engineNames = foundApp->second.vulkanEngineNames;
+    if (engineNames.size() < GpuStatsAppInfo::MAX_VULKAN_ENGINE_NAMES
+        && std::find(engineNames.cbegin(), engineNames.cend(), engineName) == engineNames.cend()) {
+        engineNames.push_back(engineName);
+    }
+}
+
 void GpuStats::insertTargetStatsArray(const std::string& appPackageName,
                                  const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats,
                                  const uint64_t* values, const uint32_t valueCount) {
@@ -389,6 +416,11 @@
             std::string angleDriverBytes = int64VectorToProtoByteString(
                 ele.second.angleDriverLoadingTime);
 
+            std::vector<const char*> engineNames;
+            for (const std::string &engineName : ele.second.vulkanEngineNames) {
+                engineNames.push_back(engineName.c_str());
+            }
+
             android::util::addAStatsEvent(
                     data,
                     android::util::GPU_STATS_APP_INFO,
@@ -410,7 +442,8 @@
                     ele.second.vulkanApiVersion,
                     ele.second.vulkanDeviceFeaturesEnabled,
                     ele.second.vulkanInstanceExtensions,
-                    ele.second.vulkanDeviceExtensions);
+                    ele.second.vulkanDeviceExtensions,
+                    engineNames);
         }
     }
 
diff --git a/services/gpuservice/gpustats/include/gpustats/GpuStats.h b/services/gpuservice/gpustats/include/gpustats/GpuStats.h
index 22c64db..961f011 100644
--- a/services/gpuservice/gpustats/include/gpustats/GpuStats.h
+++ b/services/gpuservice/gpustats/include/gpustats/GpuStats.h
@@ -44,6 +44,9 @@
     void insertTargetStatsArray(const std::string& appPackageName,
                            const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats,
                            const uint64_t* values, const uint32_t valueCount);
+    // Add the engine name passed in VkApplicationInfo during CreateInstance
+    void addVulkanEngineName(const std::string& appPackageName,
+                             const uint64_t driverVersionCode, const char* engineName);
     // dumpsys interface
     void dump(const Vector<String16>& args, std::string* result);
 
diff --git a/services/gpuservice/include/gpuservice/GpuService.h b/services/gpuservice/include/gpuservice/GpuService.h
index 54f8f66..3072885 100644
--- a/services/gpuservice/include/gpuservice/GpuService.h
+++ b/services/gpuservice/include/gpuservice/GpuService.h
@@ -64,6 +64,8 @@
     void setUpdatableDriverPath(const std::string& driverPath) override;
     std::string getUpdatableDriverPath() override;
     void toggleAngleAsSystemDriver(bool enabled) override;
+    void addVulkanEngineName(const std::string& appPackageName, const uint64_t driverVersionCode,
+                             const char *engineName) override;
 
     /*
      * IBinder interface
diff --git a/services/gpuservice/tests/unittests/GpuStatsTest.cpp b/services/gpuservice/tests/unittests/GpuStatsTest.cpp
index 4ce533f..b367457 100644
--- a/services/gpuservice/tests/unittests/GpuStatsTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuStatsTest.cpp
@@ -46,6 +46,8 @@
 #define UPDATED_DRIVER_VER_CODE   1
 #define UPDATED_DRIVER_BUILD_TIME 234
 #define VULKAN_VERSION            345
+#define VULKAN_ENGINE_NAME_1      "testVulkanEngine1"
+#define VULKAN_ENGINE_NAME_2      "testVulkanEngine2"
 #define APP_PKG_NAME_1            "testapp1"
 #define APP_PKG_NAME_2            "testapp2"
 #define DRIVER_LOADING_TIME_1     678
@@ -243,6 +245,8 @@
     mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
                                  GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION,
                                  VULKAN_DEVICE_EXTENSION_1);
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                             VULKAN_ENGINE_NAME_1);
 
     EXPECT_TRUE(inputCommand(InputCommand::DUMP_APP).empty());
 }
@@ -282,6 +286,8 @@
     mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
                                  GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION,
                                  VULKAN_DEVICE_EXTENSION_2);
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                             VULKAN_ENGINE_NAME_1);
 
     EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1"));
     EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("falsePrerotation = 1"));
@@ -302,6 +308,64 @@
     expectedResult.str("");
     expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1
                     << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2;
+    expectedResult.str("");
+    expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ",";
+
+    EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
+}
+
+// Verify the vulkanEngineNames list behaves like a set and dedupes additions
+TEST_F(GpuStatsTest, vulkanEngineNamesBehavesLikeSet) {
+    mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME,
+                                 BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1,
+                                 VULKAN_VERSION, GpuStatsInfo::Driver::GL, true,
+                                 DRIVER_LOADING_TIME_1);
+    for (int i = 0; i < 4; i++) {
+        mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                       VULKAN_ENGINE_NAME_1);
+    }
+
+    std::stringstream wrongResult, expectedResult;
+    wrongResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", " <<
+                   VULKAN_ENGINE_NAME_1;
+    expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1;
+
+    EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), Not(HasSubstr(wrongResult.str())));
+    EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
+}
+
+TEST_F(GpuStatsTest, vulkanEngineNamesCheckEmptyEngineNameAlone) {
+    mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME,
+                                 BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1,
+                                 VULKAN_VERSION, GpuStatsInfo::Driver::GL, true,
+                                 DRIVER_LOADING_TIME_1);
+
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                   "");
+
+    std::stringstream expectedResult;
+    expectedResult << "vulkanEngineNames: ,";
+
+    EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
+}
+
+TEST_F(GpuStatsTest, vulkanEngineNamesCheckEmptyEngineNameWithOthers) {
+    mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME,
+                                 BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1,
+                                 VULKAN_VERSION, GpuStatsInfo::Driver::GL, true,
+                                 DRIVER_LOADING_TIME_1);
+
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                   VULKAN_ENGINE_NAME_1);
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                   "");
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                   VULKAN_ENGINE_NAME_2);
+
+    std::stringstream expectedResult;
+    expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", "
+                   << ", " <<  VULKAN_ENGINE_NAME_2;
+
     EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
 }
 
@@ -350,6 +414,10 @@
         mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE,
                                     GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION,
                                     VULKAN_DEVICE_EXTENSION_2);
+        mGpuStats->addVulkanEngineName(fullPkgName, BUILTIN_DRIVER_VER_CODE,
+                                 VULKAN_ENGINE_NAME_1);
+        mGpuStats->addVulkanEngineName(fullPkgName, BUILTIN_DRIVER_VER_CODE,
+                                 VULKAN_ENGINE_NAME_2);
 
         EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(fullPkgName.c_str()));
         EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1"));
@@ -371,6 +439,9 @@
         expectedResult.str("");
         expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1
                         << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2;
+        expectedResult.str("");
+        expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", "
+                       << VULKAN_ENGINE_NAME_2 << ",";
         EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
     }
 
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 7f2d03d..70801dc 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -14,6 +14,7 @@
 
 // Default flags to be used throughout all libraries in inputflinger.
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
@@ -55,6 +56,16 @@
         host: {
             sanitize: {
                 address: true,
+                diag: {
+                    cfi: true,
+                    integer_overflow: true,
+                    memtag_heap: true,
+                    undefined: true,
+                    misc_undefined: [
+                        "bounds",
+                        "all",
+                    ],
+                },
             },
             include_dirs: [
                 "bionic/libc/kernel/android/uapi/",
@@ -77,6 +88,7 @@
         "InputCommonConverter.cpp",
         "InputDeviceMetricsCollector.cpp",
         "InputFilter.cpp",
+        "InputFilterCallbacks.cpp",
         "InputProcessor.cpp",
         "PointerChoreographer.cpp",
         "PreferStylusOverTouchBlocker.cpp",
@@ -105,10 +117,12 @@
         "libutils",
         "libstatspull",
         "libstatssocket",
+        "packagemanager_aidl-cpp",
         "server_configurable_flags",
     ],
     static_libs: [
         "libattestation",
+        "libperfetto_client_experimental",
         "libpalmrejection",
         "libui-types",
     ],
@@ -267,5 +281,6 @@
         "FrameworksServicesTests",
         "CtsSecurityTestCases",
         "CtsSecurityBulletinHostTestCases",
+        "monkey_test",
     ],
 }
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 6ccd9e7..e376734 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -20,6 +20,9 @@
 
 namespace android {
 
+const static ui::Transform kIdentityTransform;
+const static std::array<uint8_t, 32> kInvalidHmac{};
+
 static common::Source getSource(uint32_t source) {
     static_assert(static_cast<common::Source>(AINPUT_SOURCE_UNKNOWN) == common::Source::UNKNOWN,
                   "SOURCE_UNKNOWN mismatch");
@@ -311,7 +314,7 @@
     common::MotionEvent event;
     event.deviceId = args.deviceId;
     event.source = getSource(args.source);
-    event.displayId = args.displayId;
+    event.displayId = args.displayId.val();
     event.downTime = args.downTime;
     event.eventTime = args.eventTime;
     event.deviceTimestamp = 0;
@@ -337,4 +340,31 @@
     return event;
 }
 
+MotionEvent toMotionEvent(const NotifyMotionArgs& args, const ui::Transform* transform,
+                          const ui::Transform* rawTransform, const std::array<uint8_t, 32>* hmac) {
+    if (transform == nullptr) transform = &kIdentityTransform;
+    if (rawTransform == nullptr) rawTransform = &kIdentityTransform;
+    if (hmac == nullptr) hmac = &kInvalidHmac;
+
+    MotionEvent event;
+    event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+                     args.actionButton, args.flags, args.edgeFlags, args.metaState,
+                     args.buttonState, args.classification, *transform, args.xPrecision,
+                     args.yPrecision, args.xCursorPosition, args.yCursorPosition, *rawTransform,
+                     args.downTime, args.eventTime, args.getPointerCount(),
+                     args.pointerProperties.data(), args.pointerCoords.data());
+    return event;
+}
+
+KeyEvent toKeyEvent(const NotifyKeyArgs& args, int32_t repeatCount,
+                    const std::array<uint8_t, 32>* hmac) {
+    if (hmac == nullptr) hmac = &kInvalidHmac;
+
+    KeyEvent event;
+    event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+                     args.flags, args.keyCode, args.scanCode, args.metaState, repeatCount,
+                     args.downTime, args.eventTime);
+    return event;
+}
+
 } // namespace android
diff --git a/services/inputflinger/InputCommonConverter.h b/services/inputflinger/InputCommonConverter.h
index 4d3b768..0d4cbb0 100644
--- a/services/inputflinger/InputCommonConverter.h
+++ b/services/inputflinger/InputCommonConverter.h
@@ -16,16 +16,25 @@
 
 #pragma once
 
+#include "InputListener.h"
+
 #include <aidl/android/hardware/input/common/Axis.h>
 #include <aidl/android/hardware/input/common/MotionEvent.h>
-#include "InputListener.h"
+#include <input/Input.h>
 
 namespace android {
 
-/**
- * Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent
- */
+/** Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent. */
 ::aidl::android::hardware::input::common::MotionEvent notifyMotionArgsToHalMotionEvent(
         const NotifyMotionArgs& args);
 
+/** Convert from NotifyMotionArgs to MotionEvent. */
+MotionEvent toMotionEvent(const NotifyMotionArgs&, const ui::Transform* transform = nullptr,
+                          const ui::Transform* rawTransform = nullptr,
+                          const std::array<uint8_t, 32>* hmac = nullptr);
+
+/** Convert from NotifyKeyArgs to KeyEvent. */
+KeyEvent toKeyEvent(const NotifyKeyArgs&, int32_t repeatCount = 0,
+                    const std::array<uint8_t, 32>* hmac = nullptr);
+
 } // namespace android
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 9c4a3eb..8e73ce5 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -32,7 +32,7 @@
     event.eventTime = args.eventTime;
     event.deviceId = args.deviceId;
     event.source = static_cast<Source>(args.source);
-    event.displayId = args.displayId;
+    event.displayId = args.displayId.val();
     event.policyFlags = args.policyFlags;
     event.action = static_cast<KeyEventAction>(args.action);
     event.flags = args.flags;
@@ -44,45 +44,25 @@
     return event;
 }
 
-NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
-    return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
-                         static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
-                         static_cast<int32_t>(event.action), event.flags, event.keyCode,
-                         event.scanCode, event.metaState, event.downTime);
-}
-
-namespace {
-
-class RustCallbacks : public IInputFilter::BnInputFilterCallbacks {
-public:
-    RustCallbacks(InputListenerInterface& nextListener) : mNextListener(nextListener) {}
-    ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override {
-        mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
-        return ndk::ScopedAStatus::ok();
-    }
-
-private:
-    InputListenerInterface& mNextListener;
-};
-
-} // namespace
-
-InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust)
-      : mNextListener(listener), mCallbacks(ndk::SharedRefBase::make<RustCallbacks>(listener)) {
+InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust,
+                         InputFilterPolicyInterface& policy)
+      : mNextListener(listener),
+        mCallbacks(ndk::SharedRefBase::make<InputFilterCallbacks>(listener, policy)),
+        mPolicy(policy) {
     LOG_ALWAYS_FATAL_IF(!rust.createInputFilter(mCallbacks, &mInputFilterRust).isOk());
     LOG_ALWAYS_FATAL_IF(!mInputFilterRust);
 }
 
 void InputFilter::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    mDeviceInfos.clear();
+    mDeviceInfos.reserve(args.inputDeviceInfos.size());
+    for (auto info : args.inputDeviceInfos) {
+        AidlDeviceInfo& aidlInfo = mDeviceInfos.emplace_back();
+        aidlInfo.deviceId = info.getId();
+        aidlInfo.external = info.isExternal();
+    }
     if (isFilterEnabled()) {
-        std::vector<AidlDeviceInfo> deviceInfos;
-        for (auto info : args.inputDeviceInfos) {
-            AidlDeviceInfo aidlInfo;
-            aidlInfo.deviceId = info.getId();
-            aidlInfo.external = info.isExternal();
-            deviceInfos.push_back(aidlInfo);
-        }
-        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(deviceInfos).isOk());
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(mDeviceInfos).isOk());
     }
     mNextListener.notify(args);
 }
@@ -92,11 +72,11 @@
 }
 
 void InputFilter::notifyKey(const NotifyKeyArgs& args) {
-    if (!isFilterEnabled()) {
-        mNextListener.notifyKey(args);
+    if (isFilterEnabled()) {
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
         return;
     }
-    LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
+    mNextListener.notify(args);
 }
 
 void InputFilter::notifyMotion(const NotifyMotionArgs& args) {
@@ -134,7 +114,36 @@
 
     if (mConfig.bounceKeysThresholdNs != threshold) {
         mConfig.bounceKeysThresholdNs = threshold;
-        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk());
+        notifyConfigurationChangedLocked();
+    }
+}
+
+void InputFilter::setAccessibilitySlowKeysThreshold(nsecs_t threshold) {
+    std::scoped_lock _l(mLock);
+
+    if (mConfig.slowKeysThresholdNs != threshold) {
+        mConfig.slowKeysThresholdNs = threshold;
+        notifyConfigurationChangedLocked();
+    }
+}
+
+void InputFilter::setAccessibilityStickyKeysEnabled(bool enabled) {
+    std::scoped_lock _l(mLock);
+
+    if (mConfig.stickyKeysEnabled != enabled) {
+        mConfig.stickyKeysEnabled = enabled;
+        notifyConfigurationChangedLocked();
+        if (!enabled) {
+            // When Sticky keys is disabled, send callback to clear any saved sticky state.
+            mPolicy.notifyStickyModifierStateChanged(0, 0);
+        }
+    }
+}
+
+void InputFilter::notifyConfigurationChangedLocked() {
+    LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk());
+    if (isFilterEnabled()) {
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(mDeviceInfos).isOk());
     }
 }
 
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
index 06f7d0e..4ddc9f4 100644
--- a/services/inputflinger/InputFilter.h
+++ b/services/inputflinger/InputFilter.h
@@ -18,6 +18,8 @@
 
 #include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
 #include <utils/Mutex.h>
+#include "InputFilterCallbacks.h"
+#include "InputFilterPolicyInterface.h"
 #include "InputListener.h"
 #include "NotifyArgs.h"
 
@@ -33,6 +35,8 @@
      */
     virtual void dump(std::string& dump) = 0;
     virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0;
+    virtual void setAccessibilitySlowKeysThreshold(nsecs_t threshold) = 0;
+    virtual void setAccessibilityStickyKeysEnabled(bool enabled) = 0;
 };
 
 class InputFilter : public InputFilterInterface {
@@ -43,8 +47,10 @@
             aidl::com::android::server::inputflinger::IInputFilter::IInputFilterCallbacks;
     using InputFilterConfiguration =
             aidl::com::android::server::inputflinger::InputFilterConfiguration;
+    using AidlDeviceInfo = aidl::com::android::server::inputflinger::DeviceInfo;
 
-    explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust&);
+    explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust,
+                         InputFilterPolicyInterface& policy);
     ~InputFilter() override = default;
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -56,16 +62,23 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
     void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override;
+    void setAccessibilitySlowKeysThreshold(nsecs_t threshold) override;
+    void setAccessibilityStickyKeysEnabled(bool enabled) override;
     void dump(std::string& dump) override;
 
 private:
     InputListenerInterface& mNextListener;
-    std::shared_ptr<IInputFilterCallbacks> mCallbacks;
+    std::shared_ptr<InputFilterCallbacks> mCallbacks;
+    InputFilterPolicyInterface& mPolicy;
     std::shared_ptr<IInputFilter> mInputFilterRust;
+    // Keep track of connected peripherals, so that if filters are enabled later, we can pass that
+    // info to the filters
+    std::vector<AidlDeviceInfo> mDeviceInfos;
     mutable std::mutex mLock;
     InputFilterConfiguration mConfig GUARDED_BY(mLock);
 
     bool isFilterEnabled();
+    void notifyConfigurationChangedLocked() REQUIRES(mLock);
 };
 
 } // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
new file mode 100644
index 0000000..5fbdc84
--- /dev/null
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputFilterCallbacks"
+
+#include "InputFilterCallbacks.h"
+#include <aidl/com/android/server/inputflinger/BnInputThread.h>
+#include <android/binder_auto_utils.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+#include <functional>
+#include "InputThread.h"
+
+namespace android {
+
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+
+NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
+    return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
+                         static_cast<uint32_t>(event.source), ui::LogicalDisplayId{event.displayId},
+                         event.policyFlags, static_cast<int32_t>(event.action), event.flags,
+                         event.keyCode, event.scanCode, event.metaState, event.downTime);
+}
+
+namespace {
+
+using namespace aidl::com::android::server::inputflinger;
+
+class InputFilterThread : public BnInputThread {
+public:
+    InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) {
+        mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+        mThread = std::make_unique<InputThread>(
+                "InputFilter", [this]() { loopOnce(); }, [this]() { mLooper->wake(); });
+    }
+
+    ndk::ScopedAStatus finish() override {
+        if (mThread && mThread->isCallingThread()) {
+            ALOGE("InputFilterThread cannot be stopped on itself!");
+            return ndk::ScopedAStatus::fromStatus(INVALID_OPERATION);
+        }
+        mThread.reset();
+        return ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus sleepUntil(nsecs_t when) override {
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+        mLooper->pollOnce(toMillisecondTimeoutDelay(now, when));
+        return ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus wake() override {
+        mLooper->wake();
+        return ndk::ScopedAStatus::ok();
+    }
+
+private:
+    sp<Looper> mLooper;
+    std::unique_ptr<InputThread> mThread;
+    std::shared_ptr<IInputThreadCallback> mCallback;
+
+    void loopOnce() { LOG_ALWAYS_FATAL_IF(!mCallback->loopOnce().isOk()); }
+};
+
+} // namespace
+
+InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener,
+                                           InputFilterPolicyInterface& policy)
+      : mNextListener(listener), mPolicy(policy) {}
+
+ndk::ScopedAStatus InputFilterCallbacks::sendKeyEvent(const AidlKeyEvent& event) {
+    mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus InputFilterCallbacks::onModifierStateChanged(int32_t modifierState,
+                                                                int32_t lockedModifierState) {
+    std::scoped_lock _l(mLock);
+    mStickyModifierState.modifierState = modifierState;
+    mStickyModifierState.lockedModifierState = lockedModifierState;
+    mPolicy.notifyStickyModifierStateChanged(modifierState, lockedModifierState);
+    ALOGI("Sticky keys modifier state changed: modifierState=%d, lockedModifierState=%d",
+          modifierState, lockedModifierState);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus InputFilterCallbacks::createInputFilterThread(
+        const std::shared_ptr<IInputThreadCallback>& callback,
+        std::shared_ptr<IInputThread>* aidl_return) {
+    *aidl_return = ndk::SharedRefBase::make<InputFilterThread>(callback);
+    return ndk::ScopedAStatus::ok();
+}
+
+uint32_t InputFilterCallbacks::getModifierState() {
+    std::scoped_lock _l(mLock);
+    return mStickyModifierState.modifierState;
+}
+
+uint32_t InputFilterCallbacks::getLockedModifierState() {
+    std::scoped_lock _l(mLock);
+    return mStickyModifierState.lockedModifierState;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.h b/services/inputflinger/InputFilterCallbacks.h
new file mode 100644
index 0000000..a74955b
--- /dev/null
+++ b/services/inputflinger/InputFilterCallbacks.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <android/binder_auto_utils.h>
+#include <utils/Mutex.h>
+#include <memory>
+#include <mutex>
+#include "InputFilterPolicyInterface.h"
+#include "InputListener.h"
+#include "NotifyArgs.h"
+
+/**
+ * The C++ component of InputFilter designed as a wrapper around the rust callback implementation.
+ */
+namespace android {
+
+using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+using aidl::com::android::server::inputflinger::IInputThread;
+using IInputThreadCallback =
+        aidl::com::android::server::inputflinger::IInputThread::IInputThreadCallback;
+
+class InputFilterCallbacks : public IInputFilter::BnInputFilterCallbacks {
+public:
+    explicit InputFilterCallbacks(InputListenerInterface& listener,
+                                  InputFilterPolicyInterface& policy);
+    ~InputFilterCallbacks() override = default;
+
+    uint32_t getModifierState();
+    uint32_t getLockedModifierState();
+
+private:
+    InputListenerInterface& mNextListener;
+    InputFilterPolicyInterface& mPolicy;
+    mutable std::mutex mLock;
+    struct StickyModifierState {
+        uint32_t modifierState;
+        uint32_t lockedModifierState;
+    } mStickyModifierState GUARDED_BY(mLock);
+
+    ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override;
+    ndk::ScopedAStatus onModifierStateChanged(int32_t modifierState,
+                                              int32_t lockedModifierState) override;
+    ndk::ScopedAStatus createInputFilterThread(
+            const std::shared_ptr<IInputThreadCallback>& callback,
+            std::shared_ptr<IInputThread>* aidl_return) override;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 296f244..41e5247 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -41,7 +41,6 @@
 const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
         sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
 
-const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
 const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl();
 
 int32_t exceptionCodeFromStatusT(status_t status) {
@@ -127,7 +126,8 @@
  */
 InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
                            InputDispatcherPolicyInterface& dispatcherPolicy,
-                           PointerChoreographerPolicyInterface& choreographerPolicy) {
+                           PointerChoreographerPolicyInterface& choreographerPolicy,
+                           InputFilterPolicyInterface& inputFilterPolicy) {
     mInputFlingerRust = createInputFlingerRust();
 
     mDispatcher = createInputDispatcher(dispatcherPolicy);
@@ -135,7 +135,8 @@
             std::make_unique<TracedInputListener>("InputDispatcher", *mDispatcher));
 
     if (ENABLE_INPUT_FILTER_RUST) {
-        mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust);
+        mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust,
+                                                     inputFilterPolicy);
         mTracingStages.emplace_back(
                 std::make_unique<TracedInputListener>("InputFilter", *mInputFilter));
     }
@@ -150,12 +151,10 @@
     mTracingStages.emplace_back(
             std::make_unique<TracedInputListener>("InputProcessor", *mProcessor));
 
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        mChoreographer =
-                std::make_unique<PointerChoreographer>(*mTracingStages.back(), choreographerPolicy);
-        mTracingStages.emplace_back(
-                std::make_unique<TracedInputListener>("PointerChoreographer", *mChoreographer));
-    }
+    mChoreographer =
+            std::make_unique<PointerChoreographer>(*mTracingStages.back(), choreographerPolicy);
+    mTracingStages.emplace_back(
+            std::make_unique<TracedInputListener>("PointerChoreographer", *mChoreographer));
 
     mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mTracingStages.back());
     mTracingStages.emplace_back(
@@ -243,10 +242,8 @@
     dump += '\n';
     mBlocker->dump(dump);
     dump += '\n';
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        mChoreographer->dump(dump);
-        dump += '\n';
-    }
+    mChoreographer->dump(dump);
+    dump += '\n';
     mProcessor->dump(dump);
     dump += '\n';
     if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
@@ -258,13 +255,16 @@
 }
 
 // Used by tests only.
-binder::Status InputManager::createInputChannel(const std::string& name, InputChannel* outChannel) {
+binder::Status InputManager::createInputChannel(const std::string& name,
+                                                android::os::InputChannelCore* outChannel) {
     IPCThreadState* ipc = IPCThreadState::self();
-    const int uid = ipc->getCallingUid();
+    const uid_t uid = ipc->getCallingUid();
     if (uid != AID_SHELL && uid != AID_ROOT) {
-        ALOGE("Invalid attempt to register input channel over IPC"
-                "from non shell/root entity (PID: %d)", ipc->getCallingPid());
-        return binder::Status::ok();
+        LOG(ERROR) << __func__ << " can only be called by SHELL or ROOT users, "
+                   << "but was called from UID " << uid;
+        return binder::Status::
+                fromExceptionCode(EX_SECURITY,
+                                  "This uid is not allowed to call createInputChannel");
     }
 
     base::Result<std::unique_ptr<InputChannel>> channel = mDispatcher->createInputChannel(name);
@@ -272,7 +272,7 @@
         return binder::Status::fromExceptionCode(exceptionCodeFromStatusT(channel.error().code()),
                                                  channel.error().message().c_str());
     }
-    (*channel)->copyTo(*outChannel);
+    InputChannel::moveChannel(std::move(*channel), *outChannel);
     return binder::Status::ok();
 }
 
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index fa7db37..c479aaf 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -29,6 +29,7 @@
 
 #include <InputDispatcherInterface.h>
 #include <InputDispatcherPolicyInterface.h>
+#include <InputFilterPolicyInterface.h>
 #include <PointerChoreographerPolicyInterface.h>
 #include <input/Input.h>
 #include <input/InputTransport.h>
@@ -119,7 +120,8 @@
 public:
     InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
                  InputDispatcherPolicyInterface& dispatcherPolicy,
-                 PointerChoreographerPolicyInterface& choreographerPolicy);
+                 PointerChoreographerPolicyInterface& choreographerPolicy,
+                 InputFilterPolicyInterface& inputFilterPolicy);
 
     status_t start() override;
     status_t stop() override;
@@ -134,7 +136,8 @@
     void dump(std::string& dump) override;
 
     status_t dump(int fd, const Vector<String16>& args) override;
-    binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override;
+    binder::Status createInputChannel(const std::string& name,
+                                      android::os::InputChannelCore* outChannel) override;
     binder::Status removeInputChannel(const sp<IBinder>& connectionToken) override;
     binder::Status setFocusedWindow(const gui::FocusRequest&) override;
 
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index 4ec5b89..6b2627c 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -68,7 +68,7 @@
         if (currentViewport.type == type) {
             if (!result ||
                 (type == ViewportType::INTERNAL &&
-                 currentViewport.displayId == ADISPLAY_ID_DEFAULT)) {
+                 currentViewport.displayId == ui::LogicalDisplayId::DEFAULT)) {
                 result = std::make_optional(currentViewport);
             }
             count++;
@@ -93,7 +93,7 @@
 }
 
 std::optional<DisplayViewport> InputReaderConfiguration::getDisplayViewportById(
-        int32_t displayId) const {
+        ui::LogicalDisplayId displayId) const {
     for (const DisplayViewport& currentViewport : mDisplays) {
         if (currentViewport.displayId == displayId) {
             return std::make_optional(currentViewport);
diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp
index de836e9..19a4f26 100644
--- a/services/inputflinger/NotifyArgs.cpp
+++ b/services/inputflinger/NotifyArgs.cpp
@@ -43,7 +43,7 @@
 // --- NotifyKeyArgs ---
 
 NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
-                             uint32_t source, int32_t displayId, uint32_t policyFlags,
+                             uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
                              int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
                              int32_t metaState, nsecs_t downTime)
       : id(id),
@@ -64,7 +64,7 @@
 
 NotifyMotionArgs::NotifyMotionArgs(
         int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, uint32_t source,
-        int32_t displayId, uint32_t policyFlags, int32_t action, int32_t actionButton,
+        ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t action, int32_t actionButton,
         int32_t flags, int32_t metaState, int32_t buttonState, MotionClassification classification,
         int32_t edgeFlags, uint32_t pointerCount, const PointerProperties* pointerProperties,
         const PointerCoords* pointerCoords, float xPrecision, float yPrecision,
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 0be4c32..8a1eed6 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -17,7 +17,12 @@
 #define LOG_TAG "PointerChoreographer"
 
 #include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#if defined(__ANDROID__)
+#include <gui/SurfaceComposerClient.h>
+#endif
 #include <input/PrintTools.h>
+#include <unordered_set>
 
 #include "PointerChoreographer.h"
 
@@ -25,7 +30,12 @@
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+static const bool HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS =
+        input_flags::hide_pointer_indicators_for_secure_windows();
+
 namespace {
+
 bool isFromMouse(const NotifyMotionArgs& args) {
     return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
             args.pointerProperties[0].toolType == ToolType::MOUSE;
@@ -36,6 +46,11 @@
             args.pointerProperties[0].toolType == ToolType::FINGER;
 }
 
+bool isFromDrawingTablet(const NotifyMotionArgs& args) {
+    return isFromSource(args.source, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS) &&
+            isStylusToolType(args.pointerProperties[0].toolType);
+}
+
 bool isHoverAction(int32_t action) {
     return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
             action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
@@ -44,28 +59,72 @@
 bool isStylusHoverEvent(const NotifyMotionArgs& args) {
     return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
 }
+
+bool isMouseOrTouchpad(uint32_t sources) {
+    // Check if this is a mouse or touchpad, but not a drawing tablet.
+    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
+            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
+             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
+}
+
+inline void notifyPointerDisplayChange(
+        std::optional<std::tuple<ui::LogicalDisplayId, FloatPoint>> change,
+        PointerChoreographerPolicyInterface& policy) {
+    if (!change) {
+        return;
+    }
+    const auto& [displayId, cursorPosition] = *change;
+    policy.notifyPointerDisplayIdChanged(displayId, cursorPosition);
+}
+
+void setIconForController(const std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle>& icon,
+                          PointerControllerInterface& controller) {
+    if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+        if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+            LOG(FATAL) << "SpriteIcon should not be null";
+        }
+        controller.setCustomPointerIcon(*std::get<std::unique_ptr<SpriteIcon>>(icon));
+    } else {
+        controller.updatePointerIcon(std::get<PointerIconStyle>(icon));
+    }
+}
+
 } // namespace
 
 // --- PointerChoreographer ---
 
 PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
                                            PointerChoreographerPolicyInterface& policy)
-      : mTouchControllerConstructor([this]() REQUIRES(mLock) {
+      : mTouchControllerConstructor([this]() {
             return mPolicy.createPointerController(
                     PointerControllerInterface::ControllerType::TOUCH);
         }),
         mNextListener(listener),
         mPolicy(policy),
-        mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
-        mNotifiedPointerDisplayId(ADISPLAY_ID_NONE),
+        mDefaultMouseDisplayId(ui::LogicalDisplayId::DEFAULT),
+        mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID),
         mShowTouchesEnabled(false),
         mStylusPointerIconEnabled(false) {}
 
-void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+PointerChoreographer::~PointerChoreographer() {
     std::scoped_lock _l(mLock);
+    if (mWindowInfoListener == nullptr) {
+        return;
+    }
+    mWindowInfoListener->onPointerChoreographerDestroyed();
+}
 
-    mInputDeviceInfos = args.inputDeviceInfos;
-    updatePointerControllersLocked();
+void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    PointerDisplayChange pointerDisplayChange;
+
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        mInputDeviceInfos = args.inputDeviceInfos;
+        pointerDisplayChange = updatePointerControllersLocked();
+    } // release lock
+
+    notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
     mNextListener.notify(args);
 }
 
@@ -90,6 +149,8 @@
         return processMouseEventLocked(args);
     } else if (isFromTouchpad(args)) {
         return processTouchpadEventLocked(args);
+    } else if (isFromDrawingTablet(args)) {
+        processDrawingTabletEventLocked(args);
     } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
         processStylusHoverEventLocked(args);
     } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
@@ -104,25 +165,40 @@
                    << args.dump();
     }
 
-    auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
-
-    const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    pc.move(deltaX, deltaY);
-    pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
-
-    const auto [x, y] = pc.getPosition();
+    mMouseDevices.emplace(args.deviceId);
+    auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
     NotifyMotionArgs newArgs(args);
-    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-    newArgs.xCursorPosition = x;
-    newArgs.yCursorPosition = y;
     newArgs.displayId = displayId;
+
+    if (MotionEvent::isValidCursorPosition(args.xCursorPosition, args.yCursorPosition)) {
+        // This is an absolute mouse device that knows about the location of the cursor on the
+        // display, so set the cursor position to the specified location.
+        const auto [x, y] = pc.getPosition();
+        const float deltaX = args.xCursorPosition - x;
+        const float deltaY = args.yCursorPosition - y;
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
+        pc.setPosition(args.xCursorPosition, args.yCursorPosition);
+    } else {
+        // This is a relative mouse, so move the cursor by the specified amount.
+        const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+        const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+        pc.move(deltaX, deltaY);
+        const auto [x, y] = pc.getPosition();
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        newArgs.xCursorPosition = x;
+        newArgs.yCursorPosition = y;
+    }
+    if (canUnfadeOnDisplay(displayId)) {
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
     return newArgs;
 }
 
 NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
-    auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
+    mMouseDevices.emplace(args.deviceId);
+    auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
 
     NotifyMotionArgs newArgs(args);
     newArgs.displayId = displayId;
@@ -131,7 +207,9 @@
         const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
         const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
         pc.move(deltaX, deltaY);
-        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+        if (canUnfadeOnDisplay(displayId)) {
+            pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+        }
 
         const auto [x, y] = pc.getPosition();
         newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -140,7 +218,9 @@
         newArgs.yCursorPosition = y;
     } else {
         // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
-        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+        if (canUnfadeOnDisplay(displayId)) {
+            pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+        }
 
         const auto [x, y] = pc.getPosition();
         for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
@@ -155,6 +235,37 @@
     return newArgs;
 }
 
+void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
+    if (args.displayId == ui::LogicalDisplayId::INVALID) {
+        return;
+    }
+
+    if (args.getPointerCount() != 1) {
+        LOG(WARNING) << "Only drawing tablet events with a single pointer are currently supported: "
+                     << args.dump();
+    }
+
+    // Use a mouse pointer controller for drawing tablets, or create one if it doesn't exist.
+    auto [it, _] = mDrawingTabletPointersByDevice.try_emplace(args.deviceId,
+                                                              getMouseControllerConstructor(
+                                                                      args.displayId));
+    // TODO (b/325252005): Add handing for drawing tablets mouse pointer controller
+
+    PointerControllerInterface& pc = *it->second;
+
+    const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
+    const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
+    pc.setPosition(x, y);
+    if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+        // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
+        //   immediately by a DOWN event.
+        pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+        pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
+    } else if (canUnfadeOnDisplay(args.displayId)) {
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
+}
+
 /**
  * When screen is touched, fade the mouse pointer on that display. We only call fade for
  * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the
@@ -162,7 +273,7 @@
  * For touch events, we do not need to populate the cursor position.
  */
 void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) {
-    if (args.displayId == ADISPLAY_ID_NONE) {
+    if (!args.displayId.isValid()) {
         return;
     }
 
@@ -176,7 +287,11 @@
     }
 
     // Get the touch pointer controller for the device, or create one if it doesn't exist.
-    auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+    auto [it, controllerAdded] =
+            mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+    if (controllerAdded) {
+        onControllerAddedOrRemoved();
+    }
 
     PointerControllerInterface& pc = *it->second;
 
@@ -201,7 +316,7 @@
 }
 
 void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) {
-    if (args.displayId == ADISPLAY_ID_NONE) {
+    if (!args.displayId.isValid()) {
         return;
     }
 
@@ -214,6 +329,7 @@
     auto [it, _] =
             mStylusPointersByDevice.try_emplace(args.deviceId,
                                                 getStylusControllerConstructor(args.displayId));
+    // TODO (b/325252005): Add handing for stylus pointer controller
 
     PointerControllerInterface& pc = *it->second;
 
@@ -221,8 +337,11 @@
     const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
     pc.setPosition(x, y);
     if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+        // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
+        //   immediately by a DOWN event.
         pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
-    } else {
+        pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
+    } else if (canUnfadeOnDisplay(args.displayId)) {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
 }
@@ -249,11 +368,37 @@
     std::scoped_lock _l(mLock);
     mTouchPointersByDevice.erase(args.deviceId);
     mStylusPointersByDevice.erase(args.deviceId);
+    mDrawingTabletPointersByDevice.erase(args.deviceId);
+    onControllerAddedOrRemoved();
+}
+
+void PointerChoreographer::onControllerAddedOrRemoved() {
+    if (!HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS) {
+        return;
+    }
+    bool requireListener = !mTouchPointersByDevice.empty();
+    // TODO (b/325252005): Update for other types of pointer controllers
+
+    if (requireListener && mWindowInfoListener == nullptr) {
+        mWindowInfoListener = sp<PointerChoreographerDisplayInfoListener>::make(this);
+        auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
+                                          std::vector<android::gui::DisplayInfo>{});
+#if defined(__ANDROID__)
+        SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener,
+                                                                    &initialInfo);
+#endif
+        onWindowInfosChangedLocked(initialInfo.first);
+    } else if (!requireListener && mWindowInfoListener != nullptr) {
+#if defined(__ANDROID__)
+        SurfaceComposerClient::getDefault()->removeWindowInfosListener(mWindowInfoListener);
+#endif
+        mWindowInfoListener = nullptr;
+    }
 }
 
 void PointerChoreographer::notifyPointerCaptureChanged(
         const NotifyPointerCaptureChangedArgs& args) {
-    if (args.request.enable) {
+    if (args.request.isEnable()) {
         std::scoped_lock _l(mLock);
         for (const auto& [_, mousePointerController] : mMousePointersByDisplay) {
             mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
@@ -262,6 +407,12 @@
     mNextListener.notify(args);
 }
 
+void PointerChoreographer::onWindowInfosChanged(
+        const std::vector<android::gui::WindowInfo>& windowInfos) {
+    std::scoped_lock _l(mLock);
+    onWindowInfosChangedLocked(windowInfos);
+}
+
 void PointerChoreographer::dump(std::string& dump) {
     std::scoped_lock _l(mLock);
 
@@ -273,7 +424,7 @@
     dump += INDENT "MousePointerControllers:\n";
     for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
         std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT);
-        dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump;
+        dump += INDENT + displayId.toString() + " : " + pointerControllerDump;
     }
     dump += INDENT "TouchPointerControllers:\n";
     for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) {
@@ -285,10 +436,16 @@
         std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT);
         dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
     }
+    dump += INDENT "DrawingTabletControllers:\n";
+    for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) {
+        std::string pointerControllerDump = addLinePrefix(drawingTabletController->dump(), INDENT);
+        dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+    }
     dump += "\n";
 }
 
-const DisplayViewport* PointerChoreographer::findViewportByIdLocked(int32_t displayId) const {
+const DisplayViewport* PointerChoreographer::findViewportByIdLocked(
+        ui::LogicalDisplayId displayId) const {
     for (auto& viewport : mViewports) {
         if (viewport.displayId == displayId) {
             return &viewport;
@@ -297,20 +454,20 @@
     return nullptr;
 }
 
-int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisplayId) const {
-    return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
+ui::LogicalDisplayId PointerChoreographer::getTargetMouseDisplayLocked(
+        ui::LogicalDisplayId associatedDisplayId) const {
+    return associatedDisplayId.isValid() ? associatedDisplayId : mDefaultMouseDisplayId;
 }
 
-std::pair<int32_t, PointerControllerInterface&>
-PointerChoreographer::getDisplayIdAndMouseControllerLocked(int32_t associatedDisplayId) {
-    const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId);
+std::pair<ui::LogicalDisplayId, PointerControllerInterface&>
+PointerChoreographer::ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) {
+    const ui::LogicalDisplayId displayId = getTargetMouseDisplayLocked(associatedDisplayId);
 
-    // Get the mouse pointer controller for the display, or create one if it doesn't exist.
-    auto [it, emplaced] =
-            mMousePointersByDisplay.try_emplace(displayId,
-                                                getMouseControllerConstructor(displayId));
-    if (emplaced) {
-        notifyPointerDisplayIdChangedLocked();
+    auto it = mMousePointersByDisplay.find(displayId);
+    if (it == mMousePointersByDisplay.end()) {
+        it = mMousePointersByDisplay.emplace(displayId, getMouseControllerConstructor(displayId))
+                     .first;
+        // TODO (b/325252005): Add handing for mouse pointer controller
     }
 
     return {displayId, *it->second};
@@ -322,37 +479,55 @@
     return it != mInputDeviceInfos.end() ? &(*it) : nullptr;
 }
 
-void PointerChoreographer::updatePointerControllersLocked() {
-    std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
+bool PointerChoreographer::canUnfadeOnDisplay(ui::LogicalDisplayId displayId) {
+    return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end();
+}
+
+PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerControllersLocked() {
+    std::set<ui::LogicalDisplayId /*displayId*/> mouseDisplaysToKeep;
     std::set<DeviceId> touchDevicesToKeep;
     std::set<DeviceId> stylusDevicesToKeep;
+    std::set<DeviceId> drawingTabletDevicesToKeep;
 
     // Mark the displayIds or deviceIds of PointerControllers currently needed, and create
     // new PointerControllers if necessary.
     for (const auto& info : mInputDeviceInfos) {
+        if (!info.isEnabled()) {
+            // If device is disabled, we should not keep it, and should not show pointer for
+            // disabled mouse device.
+            continue;
+        }
         const uint32_t sources = info.getSources();
-        if (isFromSource(sources, AINPUT_SOURCE_MOUSE) ||
-            isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE)) {
-            const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
+        const bool isKnownMouse = mMouseDevices.count(info.getId()) != 0;
+
+        if (isMouseOrTouchpad(sources) || isKnownMouse) {
+            const ui::LogicalDisplayId displayId =
+                    getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
             mouseDisplaysToKeep.insert(displayId);
             // For mice, show the cursor immediately when the device is first connected or
             // when it moves to a new display.
             auto [mousePointerIt, isNewMousePointer] =
                     mMousePointersByDisplay.try_emplace(displayId,
                                                         getMouseControllerConstructor(displayId));
-            auto [_, isNewMouseDevice] = mMouseDevices.emplace(info.getId());
-            if (isNewMouseDevice || isNewMousePointer) {
+            // TODO (b/325252005): Add handing for mouse pointer controller
+
+            mMouseDevices.emplace(info.getId());
+            if ((!isKnownMouse || isNewMousePointer) && canUnfadeOnDisplay(displayId)) {
                 mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE);
             }
         }
         if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
-            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            info.getAssociatedDisplayId().isValid()) {
             touchDevicesToKeep.insert(info.getId());
         }
         if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled &&
-            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            info.getAssociatedDisplayId().isValid()) {
             stylusDevicesToKeep.insert(info.getId());
         }
+        if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE) &&
+            info.getAssociatedDisplayId().isValid()) {
+            drawingTabletDevicesToKeep.insert(info.getId());
+        }
     }
 
     // Remove PointerControllers no longer needed.
@@ -365,18 +540,24 @@
     std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
         return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end();
     });
+    std::erase_if(mDrawingTabletPointersByDevice, [&drawingTabletDevicesToKeep](const auto& pair) {
+        return drawingTabletDevicesToKeep.find(pair.first) == drawingTabletDevicesToKeep.end();
+    });
     std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(mLock) {
         return std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
                             [id](const auto& info) { return info.getId() == id; }) ==
                 mInputDeviceInfos.end();
     });
 
-    // Notify the policy if there's a change on the pointer display ID.
-    notifyPointerDisplayIdChangedLocked();
+    onControllerAddedOrRemoved();
+
+    // Check if we need to notify the policy if there's a change on the pointer display ID.
+    return calculatePointerDisplayChangeToNotify();
 }
 
-void PointerChoreographer::notifyPointerDisplayIdChangedLocked() {
-    int32_t displayIdToNotify = ADISPLAY_ID_NONE;
+PointerChoreographer::PointerDisplayChange
+PointerChoreographer::calculatePointerDisplayChangeToNotify() {
+    ui::LogicalDisplayId displayIdToNotify = ui::LogicalDisplayId::INVALID;
     FloatPoint cursorPosition = {0, 0};
     if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
         it != mMousePointersByDisplay.end()) {
@@ -387,53 +568,70 @@
         displayIdToNotify = pointerController->getDisplayId();
         cursorPosition = pointerController->getPosition();
     }
-
     if (mNotifiedPointerDisplayId == displayIdToNotify) {
-        return;
+        return {};
     }
-    mPolicy.notifyPointerDisplayIdChanged(displayIdToNotify, cursorPosition);
     mNotifiedPointerDisplayId = displayIdToNotify;
+    return {{displayIdToNotify, cursorPosition}};
 }
 
-void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) {
-    std::scoped_lock _l(mLock);
+void PointerChoreographer::setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) {
+    PointerDisplayChange pointerDisplayChange;
 
-    mDefaultMouseDisplayId = displayId;
-    updatePointerControllersLocked();
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        mDefaultMouseDisplayId = displayId;
+        pointerDisplayChange = updatePointerControllersLocked();
+    } // release lock
+
+    notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
 }
 
 void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
-    std::scoped_lock _l(mLock);
-    for (const auto& viewport : viewports) {
-        const int32_t displayId = viewport.displayId;
-        if (const auto it = mMousePointersByDisplay.find(displayId);
-            it != mMousePointersByDisplay.end()) {
-            it->second->setDisplayViewport(viewport);
-        }
-        for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
-            const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
-            if (info && info->getAssociatedDisplayId() == displayId) {
-                stylusPointerController->setDisplayViewport(viewport);
+    PointerDisplayChange pointerDisplayChange;
+
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        for (const auto& viewport : viewports) {
+            const ui::LogicalDisplayId displayId = viewport.displayId;
+            if (const auto it = mMousePointersByDisplay.find(displayId);
+                it != mMousePointersByDisplay.end()) {
+                it->second->setDisplayViewport(viewport);
+            }
+            for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+                const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+                if (info && info->getAssociatedDisplayId() == displayId) {
+                    stylusPointerController->setDisplayViewport(viewport);
+                }
+            }
+            for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) {
+                const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+                if (info && info->getAssociatedDisplayId() == displayId) {
+                    drawingTabletController->setDisplayViewport(viewport);
+                }
             }
         }
-    }
-    mViewports = viewports;
-    notifyPointerDisplayIdChangedLocked();
+        mViewports = viewports;
+        pointerDisplayChange = calculatePointerDisplayChangeToNotify();
+    } // release lock
+
+    notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
 }
 
 std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice(
-        int32_t associatedDisplayId) {
+        ui::LogicalDisplayId associatedDisplayId) {
     std::scoped_lock _l(mLock);
-    const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
+    const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
     if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) {
         return *viewport;
     }
     return std::nullopt;
 }
 
-FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) {
+FloatPoint PointerChoreographer::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
     std::scoped_lock _l(mLock);
-    const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
+    const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
     if (auto it = mMousePointersByDisplay.find(resolvedDisplayId);
         it != mMousePointersByDisplay.end()) {
         return it->second->getPosition();
@@ -442,26 +640,38 @@
 }
 
 void PointerChoreographer::setShowTouchesEnabled(bool enabled) {
-    std::scoped_lock _l(mLock);
-    if (mShowTouchesEnabled == enabled) {
-        return;
-    }
-    mShowTouchesEnabled = enabled;
-    updatePointerControllersLocked();
+    PointerDisplayChange pointerDisplayChange;
+
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        if (mShowTouchesEnabled == enabled) {
+            return;
+        }
+        mShowTouchesEnabled = enabled;
+        pointerDisplayChange = updatePointerControllersLocked();
+    } // release lock
+
+    notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
 }
 
 void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) {
-    std::scoped_lock _l(mLock);
-    if (mStylusPointerIconEnabled == enabled) {
-        return;
-    }
-    mStylusPointerIconEnabled = enabled;
-    updatePointerControllersLocked();
+    PointerDisplayChange pointerDisplayChange;
+
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        if (mStylusPointerIconEnabled == enabled) {
+            return;
+        }
+        mStylusPointerIconEnabled = enabled;
+        pointerDisplayChange = updatePointerControllersLocked();
+    } // release lock
+
+    notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
 }
 
 bool PointerChoreographer::setPointerIcon(
-        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
-        DeviceId deviceId) {
+        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+        ui::LogicalDisplayId displayId, DeviceId deviceId) {
     std::scoped_lock _l(mLock);
     if (deviceId < 0) {
         LOG(WARNING) << "Invalid device id " << deviceId << ". Cannot set pointer icon.";
@@ -474,46 +684,86 @@
         return false;
     }
     const uint32_t sources = info->getSources();
-    const auto stylusPointerIt = mStylusPointersByDevice.find(deviceId);
 
-    if (isFromSource(sources, AINPUT_SOURCE_STYLUS) &&
-        stylusPointerIt != mStylusPointersByDevice.end()) {
-        if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
-            if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
-                LOG(FATAL) << "SpriteIcon should not be null";
-            }
-            stylusPointerIt->second->setCustomPointerIcon(
-                    *std::get<std::unique_ptr<SpriteIcon>>(icon));
-        } else {
-            stylusPointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+    if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE)) {
+        auto it = mDrawingTabletPointersByDevice.find(deviceId);
+        if (it != mDrawingTabletPointersByDevice.end()) {
+            setIconForController(icon, *it->second);
+            return true;
         }
-    } else if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
-        if (const auto mousePointerIt = mMousePointersByDisplay.find(displayId);
-            mousePointerIt != mMousePointersByDisplay.end()) {
-            if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
-                if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
-                    LOG(FATAL) << "SpriteIcon should not be null";
-                }
-                mousePointerIt->second->setCustomPointerIcon(
-                        *std::get<std::unique_ptr<SpriteIcon>>(icon));
-            } else {
-                mousePointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
-            }
+    }
+    if (isFromSource(sources, AINPUT_SOURCE_STYLUS)) {
+        auto it = mStylusPointersByDevice.find(deviceId);
+        if (it != mStylusPointersByDevice.end()) {
+            setIconForController(icon, *it->second);
+            return true;
+        }
+    }
+    if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
+        auto it = mMousePointersByDisplay.find(displayId);
+        if (it != mMousePointersByDisplay.end()) {
+            setIconForController(icon, *it->second);
+            return true;
         } else {
             LOG(WARNING) << "No mouse pointer controller found for display " << displayId
                          << ", device " << deviceId << ".";
             return false;
         }
-    } else {
-        LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device "
-                     << deviceId << ".";
-        return false;
     }
-    return true;
+    LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device " << deviceId
+                 << ".";
+    return false;
+}
+
+void PointerChoreographer::onWindowInfosChangedLocked(
+        const std::vector<android::gui::WindowInfo>& windowInfos) {
+    // Mark all spot controllers secure on displays containing secure windows and
+    // remove secure flag from others if required
+    std::unordered_set<ui::LogicalDisplayId> privacySensitiveDisplays;
+    std::unordered_set<ui::LogicalDisplayId> allDisplayIds;
+    for (const auto& windowInfo : windowInfos) {
+        allDisplayIds.insert(windowInfo.displayId);
+        if (!windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE) &&
+            windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)) {
+            privacySensitiveDisplays.insert(windowInfo.displayId);
+        }
+    }
+
+    for (auto& it : mTouchPointersByDevice) {
+        auto& pc = it.second;
+        for (ui::LogicalDisplayId displayId : allDisplayIds) {
+            pc->setSkipScreenshot(displayId,
+                                  privacySensitiveDisplays.find(displayId) !=
+                                          privacySensitiveDisplays.end());
+        }
+    }
+    // TODO (b/325252005): update skip screenshot flag for other types of pointer controllers
+}
+
+void PointerChoreographer::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) {
+    std::scoped_lock lock(mLock);
+    if (visible) {
+        mDisplaysWithPointersHidden.erase(displayId);
+        // We do not unfade the icons here, because we don't know when the last event happened.
+        return;
+    }
+
+    mDisplaysWithPointersHidden.emplace(displayId);
+
+    // Hide any icons that are currently visible on the display.
+    if (auto it = mMousePointersByDisplay.find(displayId); it != mMousePointersByDisplay.end()) {
+        const auto& [_, controller] = *it;
+        controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
+    for (const auto& [_, controller] : mStylusPointersByDevice) {
+        if (controller->getDisplayId() == displayId) {
+            controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
+        }
+    }
 }
 
 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
-        int32_t displayId) {
+        ui::LogicalDisplayId displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
             [this, displayId]() REQUIRES(mLock) {
                 auto pc = mPolicy.createPointerController(
@@ -527,7 +777,7 @@
 }
 
 PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor(
-        int32_t displayId) {
+        ui::LogicalDisplayId displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
             [this, displayId]() REQUIRES(mLock) {
                 auto pc = mPolicy.createPointerController(
@@ -540,4 +790,18 @@
     return ConstructorDelegate(std::move(ctor));
 }
 
+void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
+        const gui::WindowInfosUpdate& windowInfosUpdate) {
+    std::scoped_lock _l(mListenerLock);
+    if (mPointerChoreographer != nullptr) {
+        mPointerChoreographer->onWindowInfosChanged(windowInfosUpdate.windowInfos);
+    }
+}
+
+void PointerChoreographer::PointerChoreographerDisplayInfoListener::
+        onPointerChoreographerDestroyed() {
+    std::scoped_lock _l(mListenerLock);
+    mPointerChoreographer = nullptr;
+}
+
 } // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index f46419e..12316c0 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -21,6 +21,7 @@
 #include "PointerChoreographerPolicyInterface.h"
 
 #include <android-base/thread_annotations.h>
+#include <gui/WindowInfosListener.h>
 #include <type_traits>
 
 namespace android {
@@ -53,11 +54,11 @@
      * Set the display that pointers, like the mouse cursor and drawing tablets,
      * should be drawn on.
      */
-    virtual void setDefaultMouseDisplayId(int32_t displayId) = 0;
+    virtual void setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) = 0;
     virtual void setDisplayViewports(const std::vector<DisplayViewport>& viewports) = 0;
     virtual std::optional<DisplayViewport> getViewportForPointerDevice(
-            int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
-    virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0;
+            ui::LogicalDisplayId associatedDisplayId = ui::LogicalDisplayId::INVALID) = 0;
+    virtual FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) = 0;
     virtual void setShowTouchesEnabled(bool enabled) = 0;
     virtual void setStylusPointerIconEnabled(bool enabled) = 0;
     /**
@@ -66,7 +67,12 @@
      * Returns true if the icon was changed successfully, false otherwise.
      */
     virtual bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
-                                int32_t displayId, DeviceId deviceId) = 0;
+                                ui::LogicalDisplayId displayId, DeviceId deviceId) = 0;
+    /**
+     * Set whether pointer icons for mice, touchpads, and styluses should be visible on the
+     * given display.
+     */
+    virtual void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) = 0;
 
     /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
@@ -78,17 +84,18 @@
 public:
     explicit PointerChoreographer(InputListenerInterface& listener,
                                   PointerChoreographerPolicyInterface&);
-    ~PointerChoreographer() override = default;
+    ~PointerChoreographer() override;
 
-    void setDefaultMouseDisplayId(int32_t displayId) override;
+    void setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) override;
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override;
     std::optional<DisplayViewport> getViewportForPointerDevice(
-            int32_t associatedDisplayId) override;
-    FloatPoint getMouseCursorPosition(int32_t displayId) override;
+            ui::LogicalDisplayId associatedDisplayId) override;
+    FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) override;
     void setShowTouchesEnabled(bool enabled) override;
     void setStylusPointerIconEnabled(bool enabled) override;
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
-                        int32_t displayId, DeviceId deviceId) override;
+                        ui::LogicalDisplayId displayId, DeviceId deviceId) override;
+    void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -100,49 +107,79 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
+    // Public because it's also used by tests to simulate the WindowInfosListener callback
+    void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>& windowInfos);
+
     void dump(std::string& dump) override;
 
 private:
-    void updatePointerControllersLocked() REQUIRES(mLock);
-    void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock);
-    const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
-    int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
-    std::pair<int32_t, PointerControllerInterface&> getDisplayIdAndMouseControllerLocked(
-            int32_t associatedDisplayId) REQUIRES(mLock);
+    using PointerDisplayChange = std::optional<
+            std::tuple<ui::LogicalDisplayId /*displayId*/, FloatPoint /*cursorPosition*/>>;
+    [[nodiscard]] PointerDisplayChange updatePointerControllersLocked() REQUIRES(mLock);
+    [[nodiscard]] PointerDisplayChange calculatePointerDisplayChangeToNotify() REQUIRES(mLock);
+    const DisplayViewport* findViewportByIdLocked(ui::LogicalDisplayId displayId) const
+            REQUIRES(mLock);
+    ui::LogicalDisplayId getTargetMouseDisplayLocked(ui::LogicalDisplayId associatedDisplayId) const
+            REQUIRES(mLock);
+    std::pair<ui::LogicalDisplayId /*displayId*/, PointerControllerInterface&>
+    ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) REQUIRES(mLock);
     InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
+    bool canUnfadeOnDisplay(ui::LogicalDisplayId displayId) REQUIRES(mLock);
 
     NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
     NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    void processDrawingTabletEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processDeviceReset(const NotifyDeviceResetArgs& args);
+    void onControllerAddedOrRemoved() REQUIRES(mLock);
+    void onWindowInfosChangedLocked(const std::vector<android::gui::WindowInfo>& windowInfos)
+            REQUIRES(mLock);
+
+    class PointerChoreographerDisplayInfoListener : public gui::WindowInfosListener {
+    public:
+        explicit PointerChoreographerDisplayInfoListener(PointerChoreographer* pc)
+              : mPointerChoreographer(pc){};
+        void onWindowInfosChanged(const gui::WindowInfosUpdate&) override;
+        void onPointerChoreographerDestroyed();
+
+    private:
+        std::mutex mListenerLock;
+        PointerChoreographer* mPointerChoreographer GUARDED_BY(mListenerLock);
+    };
+    sp<PointerChoreographerDisplayInfoListener> mWindowInfoListener GUARDED_BY(mLock);
 
     using ControllerConstructor =
             ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
     ControllerConstructor mTouchControllerConstructor GUARDED_BY(mLock);
-    ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock);
-    ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock);
+    ControllerConstructor getMouseControllerConstructor(ui::LogicalDisplayId displayId)
+            REQUIRES(mLock);
+    ControllerConstructor getStylusControllerConstructor(ui::LogicalDisplayId displayId)
+            REQUIRES(mLock);
 
     std::mutex mLock;
 
     InputListenerInterface& mNextListener;
     PointerChoreographerPolicyInterface& mPolicy;
 
-    std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay
-            GUARDED_BY(mLock);
+    std::map<ui::LogicalDisplayId, std::shared_ptr<PointerControllerInterface>>
+            mMousePointersByDisplay GUARDED_BY(mLock);
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice
             GUARDED_BY(mLock);
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
             GUARDED_BY(mLock);
+    std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mDrawingTabletPointersByDevice
+            GUARDED_BY(mLock);
 
-    int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
-    int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
+    ui::LogicalDisplayId mDefaultMouseDisplayId GUARDED_BY(mLock);
+    ui::LogicalDisplayId mNotifiedPointerDisplayId GUARDED_BY(mLock);
     std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock);
     std::set<DeviceId> mMouseDevices GUARDED_BY(mLock);
     std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
     bool mShowTouchesEnabled GUARDED_BY(mLock);
     bool mStylusPointerIconEnabled GUARDED_BY(mLock);
+    std::set<ui::LogicalDisplayId /*displayId*/> mDisplaysWithPointersHidden;
 };
 
 } // namespace android
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 513cbfa..a4dd909 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -1,10 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsWindowManagerDeviceWindow",
+      "name": "CtsWindowManagerDeviceInput",
       "options": [
         {
-          "include-filter": "android.server.wm.window.WindowInputTests"
+          "include-filter": "android.server.wm.input.WindowInputTests"
         }
       ]
     },
@@ -146,6 +146,9 @@
           "include-filter": "android.security.cts.Poc19_03#testPocBug_115739809"
         }
       ]
+    },
+    {
+      "name": "monkey_test"
     }
   ],
   "postsubmit": [
@@ -284,6 +287,14 @@
     },
     {
       "name": "CtsInputHostTestCases"
+    },
+    {
+      "name": "monkey_test"
+    }
+  ],
+  "staged-platinum-postsubmit": [
+    {
+      "name": "inputflinger_tests"
     }
   ]
 }
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
index 14b41cd..994d1c4 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -17,6 +17,8 @@
 package com.android.server.inputflinger;
 
 import com.android.server.inputflinger.DeviceInfo;
+import com.android.server.inputflinger.IInputThread;
+import com.android.server.inputflinger.IInputThread.IInputThreadCallback;
 import com.android.server.inputflinger.InputFilterConfiguration;
 import com.android.server.inputflinger.KeyEvent;
 
@@ -33,6 +35,12 @@
     interface IInputFilterCallbacks {
         /** Sends back a filtered key event */
         void sendKeyEvent(in KeyEvent event);
+
+        /** Sends back modifier state */
+        void onModifierStateChanged(int modifierState, int lockedModifierState);
+
+        /** Creates an Input filter thread */
+        IInputThread createInputFilterThread(in IInputThreadCallback callback);
     }
 
     /** Returns if InputFilter is enabled */
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
new file mode 100644
index 0000000..cc0592e
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+/** Interface to handle and run things on an InputThread
+  * Exposes main functionality of InputThread.h to rust which internally used system/core/libutils
+  * infrastructure.
+  *
+  * <p>
+  * Earlier, we used rust thread park()/unpark() to put the thread to sleep and wake up from sleep.
+  * But that caused some breakages after migrating the rust system crates to 2021 edition. Since,
+  * the threads are created in C++, it was more reliable to rely on C++ side of the implementation
+  * to implement the sleep and wake functions.
+  * </p>
+  *
+  * <p>
+  * NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
+  * have JNI support and can't call into Java policy that we use currently. libutils provided
+  * Thread.h also recommends against using std::thread and using the provided infrastructure that
+  * already provides way of attaching JniEnv to the created thread. So, we are using this interface
+  * to expose the InputThread infrastructure to rust.
+  * </p>
+  * TODO(b/321769871): Implement the threading infrastructure with JniEnv support in rust
+  */
+interface IInputThread {
+    /** Finish input thread (if not running, this call does nothing) */
+    void finish();
+
+    /** Wakes up the thread (if sleeping) */
+    void wake();
+
+    /**
+      * Puts the thread to sleep until a future time provided.
+      *
+      * NOTE: The thread can be awaken before the provided time using {@link wake()} function.
+      */
+    void sleepUntil(long whenNanos);
+
+    /** Callbacks from C++ to call into inputflinger rust components */
+    interface IInputThreadCallback {
+        /**
+          * The created thread will keep looping and calling this function.
+          * It's the responsibility of RUST component to appropriately put the thread to sleep and
+          * wake according to the use case.
+          */
+        void loopOnce();
+    }
+}
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
index 3b2e88b..9984a6a 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
@@ -22,4 +22,8 @@
 parcelable InputFilterConfiguration {
     // Threshold value for Bounce keys filter (check bounce_keys_filter.rs)
     long bounceKeysThresholdNs;
+    // If sticky keys filter is enabled (check sticky_keys_filter.rs)
+    boolean stickyKeysEnabled;
+    // Threshold value for Slow keys filter (check slow_keys_filter.rs)
+    long slowKeysThresholdNs;
 }
\ No newline at end of file
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index 2d12574..4385072 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -11,6 +11,7 @@
 cc_benchmark {
     name: "inputflinger_benchmarks",
     srcs: [
+        ":inputdispatcher_common_test_sources",
         "InputDispatcher_benchmarks.cpp",
     ],
     defaults: [
@@ -31,6 +32,8 @@
     ],
     static_libs: [
         "libattestation",
+        "libgmock",
+        "libgtest",
         "libinputdispatcher",
     ],
 }
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 5ae3715..96c8640 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -18,11 +18,10 @@
 
 #include <android/os/IInputConstants.h>
 #include <binder/Binder.h>
-#include <gui/constants.h>
 #include "../dispatcher/InputDispatcher.h"
 #include "../tests/FakeApplicationHandle.h"
 #include "../tests/FakeInputDispatcherPolicy.h"
-#include "../tests/FakeWindowHandle.h"
+#include "../tests/FakeWindows.h"
 
 using android::base::Result;
 using android::gui::WindowInfo;
@@ -38,7 +37,7 @@
 constexpr DeviceId DEVICE_ID = 1;
 
 // An arbitrary display id
-constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s;
 
@@ -63,7 +62,7 @@
     ui::Transform identityTransform;
     MotionEvent event;
     event.initialize(IInputConstants::INVALID_INPUT_EVENT_ID, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
-                     ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
+                     ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
                      /* actionButton */ 0, /* flags */ 0,
                      /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
                      identityTransform, /* xPrecision */ 0,
@@ -89,7 +88,7 @@
     const nsecs_t currentTime = now();
     // Define a valid motion event.
     NotifyMotionArgs args(IInputConstants::INVALID_INPUT_EVENT_ID, currentTime, currentTime,
-                          DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                          DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                           POLICY_FLAG_PASS_TO_USER, AMOTION_EVENT_ACTION_DOWN,
                           /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
                           MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
@@ -104,16 +103,16 @@
 static void benchmarkNotifyMotion(benchmark::State& state) {
     // Create dispatcher
     FakeInputDispatcherPolicy fakePolicy;
-    InputDispatcher dispatcher(fakePolicy);
-    dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
-    dispatcher.start();
+    auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
+    dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+    dispatcher->start();
 
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
-    dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    dispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     NotifyMotionArgs motionArgs = generateMotionArgs();
 
@@ -122,60 +121,60 @@
         motionArgs.action = AMOTION_EVENT_ACTION_DOWN;
         motionArgs.downTime = now();
         motionArgs.eventTime = motionArgs.downTime;
-        dispatcher.notifyMotion(motionArgs);
+        dispatcher->notifyMotion(motionArgs);
 
         // Send ACTION_UP
         motionArgs.action = AMOTION_EVENT_ACTION_UP;
         motionArgs.eventTime = now();
-        dispatcher.notifyMotion(motionArgs);
+        dispatcher->notifyMotion(motionArgs);
 
-        window->consumeMotion();
-        window->consumeMotion();
+        window->consumeMotionEvent();
+        window->consumeMotionEvent();
     }
 
-    dispatcher.stop();
+    dispatcher->stop();
 }
 
 static void benchmarkInjectMotion(benchmark::State& state) {
     // Create dispatcher
     FakeInputDispatcherPolicy fakePolicy;
-    InputDispatcher dispatcher(fakePolicy);
-    dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
-    dispatcher.start();
+    auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
+    dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+    dispatcher->start();
 
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
-    dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    dispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     for (auto _ : state) {
         MotionEvent event = generateMotionEvent();
         // Send ACTION_DOWN
-        dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
-                                    INJECT_EVENT_TIMEOUT,
-                                    POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+        dispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
+                                     INJECT_EVENT_TIMEOUT,
+                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
         // Send ACTION_UP
         event.setAction(AMOTION_EVENT_ACTION_UP);
-        dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
-                                    INJECT_EVENT_TIMEOUT,
-                                    POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+        dispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
+                                     INJECT_EVENT_TIMEOUT,
+                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
-        window->consumeMotion();
-        window->consumeMotion();
+        window->consumeMotionEvent();
+        window->consumeMotionEvent();
     }
 
-    dispatcher.stop();
+    dispatcher->stop();
 }
 
 static void benchmarkOnWindowInfosChanged(benchmark::State& state) {
     // Create dispatcher
     FakeInputDispatcherPolicy fakePolicy;
-    InputDispatcher dispatcher(fakePolicy);
-    dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
-    dispatcher.start();
+    auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
+    dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+    dispatcher->start();
 
     // Create a window
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -188,12 +187,12 @@
     std::vector<gui::DisplayInfo> displayInfos{info};
 
     for (auto _ : state) {
-        dispatcher.onWindowInfosChanged(
+        dispatcher->onWindowInfosChanged(
                 {windowInfos, displayInfos, /*vsyncId=*/0, /*timestamp=*/0});
-        dispatcher.onWindowInfosChanged(
+        dispatcher->onWindowInfosChanged(
                 {/*windowInfos=*/{}, /*displayInfos=*/{}, /*vsyncId=*/{}, /*timestamp=*/0});
     }
-    dispatcher.stop();
+    dispatcher->stop();
 }
 
 } // namespace
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index c7bacee..29aa3c3 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
@@ -49,6 +50,7 @@
         "Monitor.cpp",
         "TouchedWindow.cpp",
         "TouchState.cpp",
+        "trace/*.cpp",
     ],
 }
 
@@ -57,6 +59,8 @@
     srcs: [":libinputdispatcher_sources"],
     shared_libs: [
         "libbase",
+        "libbinder",
+        "libbinder_ndk",
         "libcrypto",
         "libcutils",
         "libinput",
@@ -67,11 +71,13 @@
         "libutils",
         "libstatspull",
         "libstatssocket",
+        "packagemanager_aidl-cpp",
         "server_configurable_flags",
     ],
     static_libs: [
         "libattestation",
         "libgui_window_info_static",
+        "libperfetto_client_experimental",
     ],
     target: {
         android: {
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 83e6a60..4a0889f 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "trace/EventTrackerInterface.h"
+
 #include <input/Input.h>
 #include <bitset>
 #include <optional>
@@ -46,12 +48,18 @@
     std::optional<int32_t> deviceId = std::nullopt;
 
     // The specific display id of events to cancel, or nullopt to cancel events on any display.
-    std::optional<int32_t> displayId = std::nullopt;
+    std::optional<ui::LogicalDisplayId> displayId = std::nullopt;
 
     // The specific pointers to cancel, or nullopt to cancel all pointer events
     std::optional<std::bitset<MAX_POINTER_ID + 1>> pointerIds = std::nullopt;
 
-    CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {}
+    const std::unique_ptr<trace::EventTrackerInterface>& traceTracker;
+
+    explicit CancelationOptions(Mode mode, const char* reason,
+                                const std::unique_ptr<trace::EventTrackerInterface>& traceTracker)
+          : mode(mode), reason(reason), traceTracker(traceTracker) {}
+    CancelationOptions(const CancelationOptions&) = delete;
+    CancelationOptions operator=(const CancelationOptions&) = delete;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp
index f304712..9dee66f 100644
--- a/services/inputflinger/dispatcher/Connection.cpp
+++ b/services/inputflinger/dispatcher/Connection.cpp
@@ -20,22 +20,15 @@
 
 namespace android::inputdispatcher {
 
-Connection::Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor,
+Connection::Connection(std::unique_ptr<InputChannel> inputChannel, bool monitor,
                        const IdGenerator& idGenerator)
       : status(Status::NORMAL),
-        inputChannel(inputChannel),
         monitor(monitor),
-        inputPublisher(inputChannel),
+        inputPublisher(std::move(inputChannel)),
         inputState(idGenerator) {}
 
-const std::string Connection::getWindowName() const {
-    if (inputChannel != nullptr) {
-        return inputChannel->getName();
-    }
-    if (monitor) {
-        return "monitor";
-    }
-    return "?";
-}
+sp<IBinder> Connection::getToken() const {
+    return inputPublisher.getChannel().getConnectionToken();
+};
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h
index c17baea..a834a8c 100644
--- a/services/inputflinger/dispatcher/Connection.h
+++ b/services/inputflinger/dispatcher/Connection.h
@@ -42,7 +42,6 @@
     };
 
     Status status;
-    std::shared_ptr<InputChannel> inputChannel; // never null
     bool monitor;
     InputPublisher inputPublisher;
     InputState inputState;
@@ -59,12 +58,14 @@
     // yet received a "finished" response from the application.
     std::deque<std::unique_ptr<DispatchEntry>> waitQueue;
 
-    Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor,
+    Connection(std::unique_ptr<InputChannel> inputChannel, bool monitor,
                const IdGenerator& idGenerator);
 
-    inline const std::string getInputChannelName() const { return inputChannel->getName(); }
+    inline const std::string getInputChannelName() const {
+        return inputPublisher.getChannel().getName();
+    }
 
-    const std::string getWindowName() const;
+    sp<IBinder> getToken() const;
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index c889b9b..fe33d94 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -98,13 +98,6 @@
 constexpr bool DEBUG_TOUCH_OCCLUSION = true;
 
 /**
- * Log debug messages about the app switch latency optimization.
- * Enable this via "adb shell setprop log.tag.InputDispatcherAppSwitch DEBUG" (requires restart)
- */
-const bool DEBUG_APP_SWITCH =
-        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "AppSwitch");
-
-/**
  * Log debug messages about hover events.
  * Enable this via "adb shell setprop log.tag.InputDispatcherHover DEBUG" (requires restart)
  */
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index cc0d49c..ad9cec1 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -23,6 +23,7 @@
 
 #include <android-base/stringprintf.h>
 #include <cutils/atomic.h>
+#include <ftl/enum.h>
 #include <inttypes.h>
 
 using android::base::StringPrintf;
@@ -110,7 +111,7 @@
 
 std::string PointerCaptureChangedEntry::getDescription() const {
     return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
-                        pointerCaptureRequest.enable ? "true" : "false");
+                        pointerCaptureRequest.isEnable() ? "true" : "false");
 }
 
 // --- DragEntry ---
@@ -131,9 +132,9 @@
 // --- KeyEntry ---
 
 KeyEntry::KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
-                   int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
-                   int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
-                   int32_t metaState, int32_t repeatCount, nsecs_t downTime)
+                   int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId,
+                   uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+                   int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime)
       : EventEntry(id, Type::KEY, eventTime, policyFlags),
         deviceId(deviceId),
         source(source),
@@ -155,18 +156,25 @@
     if (!IS_DEBUGGABLE_BUILD) {
         return "KeyEvent";
     }
-    return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32
-                        ", action=%s, "
+    return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%s, "
+                        "action=%s, "
                         "flags=0x%08x, keyCode=%s(%d), scanCode=%d, metaState=0x%08x, "
                         "repeatCount=%d), policyFlags=0x%08x",
-                        deviceId, eventTime, inputEventSourceToString(source).c_str(), displayId,
-                        KeyEvent::actionToString(action), flags, KeyEvent::getLabel(keyCode),
-                        keyCode, scanCode, metaState, repeatCount, policyFlags);
+                        deviceId, eventTime, inputEventSourceToString(source).c_str(),
+                        displayId.toString().c_str(), KeyEvent::actionToString(action), flags,
+                        KeyEvent::getLabel(keyCode), keyCode, scanCode, metaState, repeatCount,
+                        policyFlags);
+}
+
+std::ostream& operator<<(std::ostream& out, const KeyEntry& keyEntry) {
+    out << keyEntry.getDescription();
+    return out;
 }
 
 // --- TouchModeEntry ---
 
-TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int displayId)
+TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode,
+                               ui::LogicalDisplayId displayId)
       : EventEntry(id, Type::TOUCH_MODE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
         inTouchMode(inTouchMode),
         displayId(displayId) {}
@@ -178,12 +186,13 @@
 // --- MotionEntry ---
 
 MotionEntry::MotionEntry(int32_t id, std::shared_ptr<InjectionState> injectionState,
-                         nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
-                         uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
-                         int32_t metaState, int32_t buttonState,
-                         MotionClassification classification, int32_t edgeFlags, float xPrecision,
-                         float yPrecision, float xCursorPosition, float yCursorPosition,
-                         nsecs_t downTime, const std::vector<PointerProperties>& pointerProperties,
+                         nsecs_t eventTime, int32_t deviceId, uint32_t source,
+                         ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t action,
+                         int32_t actionButton, int32_t flags, int32_t metaState,
+                         int32_t buttonState, MotionClassification classification,
+                         int32_t edgeFlags, float xPrecision, float yPrecision,
+                         float xCursorPosition, float yCursorPosition, nsecs_t downTime,
+                         const std::vector<PointerProperties>& pointerProperties,
                          const std::vector<PointerCoords>& pointerCoords)
       : EventEntry(id, Type::MOTION, eventTime, policyFlags),
         deviceId(deviceId),
@@ -212,15 +221,16 @@
     }
     std::string msg;
     msg += StringPrintf("MotionEvent(deviceId=%d, eventTime=%" PRIu64
-                        ", source=%s, displayId=%" PRId32
-                        ", action=%s, actionButton=0x%08x, flags=0x%08x, metaState=0x%08x, "
+                        ", source=%s, displayId=%s, action=%s, actionButton=0x%08x, flags=0x%08x,"
+                        " metaState=0x%08x, "
                         "buttonState=0x%08x, "
                         "classification=%s, edgeFlags=0x%08x, xPrecision=%.1f, yPrecision=%.1f, "
                         "xCursorPosition=%0.1f, yCursorPosition=%0.1f, pointers=[",
-                        deviceId, eventTime, inputEventSourceToString(source).c_str(), displayId,
-                        MotionEvent::actionToString(action).c_str(), actionButton, flags, metaState,
-                        buttonState, motionClassificationToString(classification), edgeFlags,
-                        xPrecision, yPrecision, xCursorPosition, yCursorPosition);
+                        deviceId, eventTime, inputEventSourceToString(source).c_str(),
+                        displayId.toString().c_str(), MotionEvent::actionToString(action).c_str(),
+                        actionButton, flags, metaState, buttonState,
+                        motionClassificationToString(classification), edgeFlags, xPrecision,
+                        yPrecision, xCursorPosition, yCursorPosition);
 
     for (uint32_t i = 0; i < getPointerCount(); i++) {
         if (i) {
@@ -256,9 +266,10 @@
 std::string SensorEntry::getDescription() const {
     std::string msg;
     msg += StringPrintf("SensorEntry(deviceId=%d, source=%s, sensorType=%s, "
-                        "accuracy=0x%08x, hwTimestamp=%" PRId64,
+                        "accuracy=%s, hwTimestamp=%" PRId64,
                         deviceId, inputEventSourceToString(source).c_str(),
-                        ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp);
+                        ftl::enum_string(sensorType).c_str(), ftl::enum_string(accuracy).c_str(),
+                        hwTimestamp);
 
     if (IS_DEBUGGABLE_BUILD) {
         for (size_t i = 0; i < values.size(); i++) {
@@ -277,9 +288,10 @@
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
 DispatchEntry::DispatchEntry(std::shared_ptr<const EventEntry> eventEntry,
-                             ftl::Flags<InputTarget::Flags> targetFlags,
+                             ftl::Flags<InputTargetFlags> targetFlags,
                              const ui::Transform& transform, const ui::Transform& rawTransform,
-                             float globalScaleFactor)
+                             float globalScaleFactor, gui::Uid targetUid, int64_t vsyncId,
+                             std::optional<int32_t> windowId)
       : seq(nextSeq()),
         eventEntry(std::move(eventEntry)),
         targetFlags(targetFlags),
@@ -287,7 +299,10 @@
         rawTransform(rawTransform),
         globalScaleFactor(globalScaleFactor),
         deliveryTime(0),
-        resolvedFlags(0) {
+        resolvedFlags(0),
+        targetUid(targetUid),
+        vsyncId(vsyncId),
+        windowId(windowId) {
     switch (this->eventEntry->type) {
         case EventEntry::Type::KEY: {
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*this->eventEntry);
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index e2e13c3..f2f31d8 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -17,7 +17,8 @@
 #pragma once
 
 #include "InjectionState.h"
-#include "InputTarget.h"
+#include "InputTargetFlags.h"
+#include "trace/EventTrackerInterface.h"
 
 #include <gui/InputApplication.h>
 #include <input/Input.h>
@@ -119,12 +120,13 @@
 struct KeyEntry : EventEntry {
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
     int32_t action;
     int32_t keyCode;
     int32_t scanCode;
     int32_t metaState;
     nsecs_t downTime;
+    std::unique_ptr<trace::EventTrackerInterface> traceTracker;
 
     bool syntheticRepeat; // set to true for synthetic key repeats
 
@@ -138,19 +140,22 @@
     mutable InterceptKeyResult interceptKeyResult; // set based on the interception result
     mutable nsecs_t interceptKeyWakeupTime;        // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
     mutable int32_t flags;
+    // TODO(b/328618922): Refactor key repeat generation to make repeatCount non-mutable.
     mutable int32_t repeatCount;
 
     KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
-             int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
-             int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
-             int32_t repeatCount, nsecs_t downTime);
+             int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId,
+             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+             int32_t metaState, int32_t repeatCount, nsecs_t downTime);
     std::string getDescription() const override;
 };
 
+std::ostream& operator<<(std::ostream& out, const KeyEntry& motionEntry);
+
 struct MotionEntry : EventEntry {
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
     int32_t action;
     int32_t actionButton;
     int32_t flags;
@@ -165,15 +170,17 @@
     nsecs_t downTime;
     std::vector<PointerProperties> pointerProperties;
     std::vector<PointerCoords> pointerCoords;
+    std::unique_ptr<trace::EventTrackerInterface> traceTracker;
 
     size_t getPointerCount() const { return pointerProperties.size(); }
 
     MotionEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
-                int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
-                int32_t action, int32_t actionButton, int32_t flags, int32_t metaState,
-                int32_t buttonState, MotionClassification classification, int32_t edgeFlags,
-                float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition,
-                nsecs_t downTime, const std::vector<PointerProperties>& pointerProperties,
+                int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId,
+                uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
+                int32_t metaState, int32_t buttonState, MotionClassification classification,
+                int32_t edgeFlags, float xPrecision, float yPrecision, float xCursorPosition,
+                float yCursorPosition, nsecs_t downTime,
+                const std::vector<PointerProperties>& pointerProperties,
                 const std::vector<PointerCoords>& pointerCoords);
     std::string getDescription() const override;
 };
@@ -199,9 +206,9 @@
 
 struct TouchModeEntry : EventEntry {
     bool inTouchMode;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
 
-    TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int32_t displayId);
+    TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, ui::LogicalDisplayId displayId);
     std::string getDescription() const override;
 };
 
@@ -210,7 +217,7 @@
     const uint32_t seq; // unique sequence number, never 0
 
     std::shared_ptr<const EventEntry> eventEntry; // the event to dispatch
-    const ftl::Flags<InputTarget::Flags> targetFlags;
+    const ftl::Flags<InputTargetFlags> targetFlags;
     ui::Transform transform;
     ui::Transform rawTransform;
     float globalScaleFactor;
@@ -222,17 +229,27 @@
 
     int32_t resolvedFlags;
 
+    // Information about the dispatch window used for tracing. We avoid holding a window handle
+    // here because information in a window handle may be dynamically updated within the lifespan
+    // of this dispatch entry.
+    gui::Uid targetUid;
+    int64_t vsyncId;
+    // The window that this event is targeting. The only case when this windowId is not populated
+    // is when dispatching an event to a global monitor.
+    std::optional<int32_t> windowId;
+
     DispatchEntry(std::shared_ptr<const EventEntry> eventEntry,
-                  ftl::Flags<InputTarget::Flags> targetFlags, const ui::Transform& transform,
-                  const ui::Transform& rawTransform, float globalScaleFactor);
+                  ftl::Flags<InputTargetFlags> targetFlags, const ui::Transform& transform,
+                  const ui::Transform& rawTransform, float globalScaleFactor, gui::Uid targetUid,
+                  int64_t vsyncId, std::optional<int32_t> windowId);
     DispatchEntry(const DispatchEntry&) = delete;
     DispatchEntry& operator=(const DispatchEntry&) = delete;
 
     inline bool hasForegroundTarget() const {
-        return targetFlags.test(InputTarget::Flags::FOREGROUND);
+        return targetFlags.test(InputTargetFlags::FOREGROUND);
     }
 
-    inline bool isSplit() const { return targetFlags.test(InputTarget::Flags::SPLIT); }
+    inline bool isSplit() const { return targetFlags.test(InputTargetFlags::SPLIT); }
 
 private:
     static volatile int32_t sNextSeqAtomic;
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp
index 0e4e79e..b374fad 100644
--- a/services/inputflinger/dispatcher/FocusResolver.cpp
+++ b/services/inputflinger/dispatcher/FocusResolver.cpp
@@ -41,12 +41,12 @@
     size_t operator()(const sp<T>& k) const { return std::hash<T*>()(k.get()); }
 };
 
-sp<IBinder> FocusResolver::getFocusedWindowToken(int32_t displayId) const {
+sp<IBinder> FocusResolver::getFocusedWindowToken(ui::LogicalDisplayId displayId) const {
     auto it = mFocusedWindowTokenByDisplay.find(displayId);
     return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr;
 }
 
-std::optional<FocusRequest> FocusResolver::getFocusRequest(int32_t displayId) {
+std::optional<FocusRequest> FocusResolver::getFocusRequest(ui::LogicalDisplayId displayId) {
     auto it = mFocusRequestByDisplay.find(displayId);
     return it != mFocusRequestByDisplay.end() ? std::make_optional<>(it->second) : std::nullopt;
 }
@@ -58,7 +58,7 @@
  * we will check if the previous focus request is eligible to receive focus.
  */
 std::optional<FocusResolver::FocusChanges> FocusResolver::setInputWindows(
-        int32_t displayId, const std::vector<sp<WindowInfoHandle>>& windows) {
+        ui::LogicalDisplayId displayId, const std::vector<sp<WindowInfoHandle>>& windows) {
     std::string removeFocusReason;
 
     const std::optional<FocusRequest> request = getFocusRequest(displayId);
@@ -94,12 +94,11 @@
 
 std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
         const FocusRequest& request, const std::vector<sp<WindowInfoHandle>>& windows) {
-    const int32_t displayId = request.displayId;
+    const ui::LogicalDisplayId displayId = ui::LogicalDisplayId{request.displayId};
     const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
     if (currentFocus == request.token) {
-        ALOGD_IF(DEBUG_FOCUS,
-                 "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",
-                 request.windowName.c_str(), displayId);
+        ALOGD_IF(DEBUG_FOCUS, "setFocusedWindow %s on display %s ignored, reason: already focused",
+                 request.windowName.c_str(), displayId.toString().c_str());
         return std::nullopt;
     }
 
@@ -193,7 +192,7 @@
 }
 
 std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow(
-        int32_t displayId, const std::string& reason, const sp<IBinder>& newFocus,
+        ui::LogicalDisplayId displayId, const std::string& reason, const sp<IBinder>& newFocus,
         const std::string& tokenName) {
     sp<IBinder> oldFocus = getFocusedWindowToken(displayId);
     if (newFocus == oldFocus) {
@@ -216,8 +215,8 @@
     std::string dump;
     dump += INDENT "FocusedWindows:\n";
     for (const auto& [displayId, namedToken] : mFocusedWindowTokenByDisplay) {
-        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s'\n", displayId,
-                                   namedToken.first.c_str());
+        dump += base::StringPrintf(INDENT2 "displayId=%s, name='%s'\n",
+                                   displayId.toString().c_str(), namedToken.first.c_str());
     }
     return dump;
 }
@@ -233,13 +232,14 @@
         auto it = mLastFocusResultByDisplay.find(displayId);
         std::string result =
                 it != mLastFocusResultByDisplay.end() ? ftl::enum_string(it->second) : "";
-        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s' result='%s'\n",
-                                   displayId, request.windowName.c_str(), result.c_str());
+        dump += base::StringPrintf(INDENT2 "displayId=%s, name='%s' result='%s'\n",
+                                   displayId.toString().c_str(), request.windowName.c_str(),
+                                   result.c_str());
     }
     return dump;
 }
 
-void FocusResolver::displayRemoved(int32_t displayId) {
+void FocusResolver::displayRemoved(ui::LogicalDisplayId displayId) {
     mFocusRequestByDisplay.erase(displayId);
     mLastFocusResultByDisplay.erase(displayId);
 }
diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h
index 5bb157b..2910ba4 100644
--- a/services/inputflinger/dispatcher/FocusResolver.h
+++ b/services/inputflinger/dispatcher/FocusResolver.h
@@ -49,22 +49,23 @@
 class FocusResolver {
 public:
     // Returns the focused window token on the specified display.
-    sp<IBinder> getFocusedWindowToken(int32_t displayId) const;
+    sp<IBinder> getFocusedWindowToken(ui::LogicalDisplayId displayId) const;
 
     struct FocusChanges {
         sp<IBinder> oldFocus;
         sp<IBinder> newFocus;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId;
         std::string reason;
     };
     std::optional<FocusResolver::FocusChanges> setInputWindows(
-            int32_t displayId, const std::vector<sp<android::gui::WindowInfoHandle>>& windows);
+            ui::LogicalDisplayId displayId,
+            const std::vector<sp<android::gui::WindowInfoHandle>>& windows);
     std::optional<FocusResolver::FocusChanges> setFocusedWindow(
             const android::gui::FocusRequest& request,
             const std::vector<sp<android::gui::WindowInfoHandle>>& windows);
 
     // Display has been removed from the system, clean up old references.
-    void displayRemoved(int32_t displayId);
+    void displayRemoved(ui::LogicalDisplayId displayId);
 
     // exposed for debugging
     bool hasFocusedWindowTokens() const { return !mFocusedWindowTokenByDisplay.empty(); }
@@ -105,20 +106,23 @@
     // the same token. Focus is tracked by the token per display and the events are dispatched
     // to the channel associated by this token.
     typedef std::pair<std::string /* name */, sp<IBinder>> NamedToken;
-    std::unordered_map<int32_t /* displayId */, NamedToken> mFocusedWindowTokenByDisplay;
+    std::unordered_map<ui::LogicalDisplayId /* displayId */, NamedToken>
+            mFocusedWindowTokenByDisplay;
 
     // This map will store the focus request per display. When the input window handles are updated,
     // the current request will be checked to see if it can be processed at that time.
-    std::unordered_map<int32_t /* displayId */, android::gui::FocusRequest> mFocusRequestByDisplay;
+    std::unordered_map<ui::LogicalDisplayId /* displayId */, android::gui::FocusRequest>
+            mFocusRequestByDisplay;
 
     // Last reason for not granting a focus request. This is used to add more debug information
     // in the event logs.
-    std::unordered_map<int32_t /* displayId */, Focusability> mLastFocusResultByDisplay;
+    std::unordered_map<ui::LogicalDisplayId /* displayId */, Focusability>
+            mLastFocusResultByDisplay;
 
     std::optional<FocusResolver::FocusChanges> updateFocusedWindow(
-            int32_t displayId, const std::string& reason, const sp<IBinder>& token,
+            ui::LogicalDisplayId displayId, const std::string& reason, const sp<IBinder>& token,
             const std::string& tokenName = "");
-    std::optional<android::gui::FocusRequest> getFocusRequest(int32_t displayId);
+    std::optional<android::gui::FocusRequest> getFocusRequest(ui::LogicalDisplayId displayId);
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 67e48a2..dae2b61 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -35,7 +35,7 @@
 #include <input/PrintTools.h>
 #include <input/TraceTools.h>
 #include <openssl/mem.h>
-#include <powermanager/PowerManager.h>
+#include <private/android_filesystem_config.h>
 #include <unistd.h>
 #include <utils/Trace.h>
 
@@ -52,6 +52,9 @@
 #include "Connection.h"
 #include "DebugConfig.h"
 #include "InputDispatcher.h"
+#include "trace/InputTracer.h"
+#include "trace/InputTracingPerfettoBackend.h"
+#include "trace/ThreadedBackend.h"
 
 #define INDENT "  "
 #define INDENT2 "    "
@@ -72,11 +75,54 @@
 using android::os::InputEventInjectionSync;
 namespace input_flags = com::android::input::flags;
 
-static const bool REMOVE_APP_SWITCH_DROPS = input_flags::remove_app_switch_drops();
-
 namespace android::inputdispatcher {
 
 namespace {
+
+// Input tracing is only available on debuggable builds (userdebug and eng) when the feature
+// flag is enabled. When the flag is changed, tracing will only be available after reboot.
+bool isInputTracingEnabled() {
+    static const std::string buildType = base::GetProperty("ro.build.type", "user");
+    static const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng";
+    return input_flags::enable_input_event_tracing() && isUserdebugOrEng;
+}
+
+// Create the input tracing backend that writes to perfetto from a single thread.
+std::unique_ptr<trace::InputTracingBackendInterface> createInputTracingBackendIfEnabled() {
+    if (!isInputTracingEnabled()) {
+        return nullptr;
+    }
+    return std::make_unique<trace::impl::ThreadedBackend<trace::impl::PerfettoBackend>>(
+            trace::impl::PerfettoBackend());
+}
+
+template <class Entry>
+void ensureEventTraced(const Entry& entry) {
+    if (!entry.traceTracker) {
+        LOG(FATAL) << "Expected event entry to be traced, but it wasn't: " << entry;
+    }
+}
+
+// Helper to get a trace tracker from a traced key or motion entry.
+const std::unique_ptr<trace::EventTrackerInterface>& getTraceTracker(const EventEntry& entry) {
+    switch (entry.type) {
+        case EventEntry::Type::MOTION: {
+            const auto& motion = static_cast<const MotionEntry&>(entry);
+            ensureEventTraced(motion);
+            return motion.traceTracker;
+        }
+        case EventEntry::Type::KEY: {
+            const auto& key = static_cast<const KeyEntry&>(entry);
+            ensureEventTraced(key);
+            return key.traceTracker;
+        }
+        default: {
+            const static std::unique_ptr<trace::EventTrackerInterface> kNullTracker;
+            return kNullTracker;
+        }
+    }
+}
+
 // Temporarily releases a held mutex for the lifetime of the instance.
 // Named to match std::scoped_lock
 class scoped_unlock {
@@ -94,10 +140,8 @@
         android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
         HwTimeoutMultiplier());
 
-// Amount of time to allow for all pending events to be processed when an app switch
-// key is on the way.  This is used to preempt input dispatch and drop input events
-// when an application takes too long to respond and the user has pressed an app switch key.
-constexpr nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+// The default minimum time gap between two user activity poke events.
+const std::chrono::milliseconds DEFAULT_USER_ACTIVITY_POKE_INTERVAL = 100ms;
 
 const std::chrono::duration STALE_EVENT_TIMEOUT = std::chrono::seconds(10) * HwTimeoutMultiplier();
 
@@ -107,11 +151,6 @@
 // Log a warning when an interception call takes longer than this to process.
 constexpr std::chrono::milliseconds SLOW_INTERCEPTION_THRESHOLD = 50ms;
 
-// Additional key latency in case a connection is still processing some motion events.
-// This will help with the case when a user touched a button that opens a new window,
-// and gives us the chance to dispatch the key to this new window.
-constexpr std::chrono::nanoseconds KEY_WAITING_FOR_EVENTS_TIMEOUT = 500ms;
-
 // Number of recent events to keep for debugging purposes.
 constexpr size_t RECENT_QUEUE_MAX_SIZE = 10;
 
@@ -252,6 +291,14 @@
     }
 }
 
+std::bitset<MAX_POINTER_ID + 1> getPointerIds(const std::vector<PointerProperties>& pointers) {
+    std::bitset<MAX_POINTER_ID + 1> pointerIds;
+    for (const PointerProperties& pointer : pointers) {
+        pointerIds.set(pointer.id);
+    }
+    return pointerIds;
+}
+
 std::string dumpRegion(const Region& region) {
     if (region.isEmpty()) {
         return "<empty>";
@@ -348,46 +395,64 @@
     return i;
 }
 
-std::unique_ptr<DispatchEntry> createDispatchEntry(
-        const InputTarget& inputTarget, std::shared_ptr<const EventEntry> eventEntry,
-        ftl::Flags<InputTarget::Flags> inputTargetFlags) {
-    if (inputTarget.useDefaultPointerTransform()) {
+std::unique_ptr<DispatchEntry> createDispatchEntry(const IdGenerator& idGenerator,
+                                                   const InputTarget& inputTarget,
+                                                   std::shared_ptr<const EventEntry> eventEntry,
+                                                   ftl::Flags<InputTarget::Flags> inputTargetFlags,
+                                                   int64_t vsyncId,
+                                                   trace::InputTracerInterface* tracer) {
+    const bool zeroCoords = inputTargetFlags.test(InputTarget::Flags::ZERO_COORDS);
+    const sp<WindowInfoHandle> win = inputTarget.windowHandle;
+    const std::optional<int32_t> windowId =
+            win ? std::make_optional(win->getInfo()->id) : std::nullopt;
+    // Assume the only targets that are not associated with a window are global monitors, and use
+    // the system UID for global monitors for tracing purposes.
+    const gui::Uid uid = win ? win->getInfo()->ownerUid : gui::Uid(AID_SYSTEM);
+
+    if (inputTarget.useDefaultPointerTransform() && !zeroCoords) {
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
         return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
                                                inputTarget.displayTransform,
-                                               inputTarget.globalScaleFactor);
+                                               inputTarget.globalScaleFactor, uid, vsyncId,
+                                               windowId);
     }
 
     ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);
     const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
 
-    std::vector<PointerCoords> pointerCoords;
-    pointerCoords.resize(motionEntry.getPointerCount());
+    std::vector<PointerCoords> pointerCoords{motionEntry.getPointerCount()};
 
-    // Use the first pointer information to normalize all other pointers. This could be any pointer
-    // as long as all other pointers are normalized to the same value and the final DispatchEntry
-    // uses the transform for the normalized pointer.
-    const ui::Transform& firstPointerTransform =
-            inputTarget.pointerTransforms[firstMarkedBit(inputTarget.pointerIds)];
-    ui::Transform inverseFirstTransform = firstPointerTransform.inverse();
+    const ui::Transform* transform = &kIdentityTransform;
+    const ui::Transform* displayTransform = &kIdentityTransform;
+    if (zeroCoords) {
+        std::for_each(pointerCoords.begin(), pointerCoords.end(), [](auto& pc) { pc.clear(); });
+    } else {
+        // Use the first pointer information to normalize all other pointers. This could be any
+        // pointer as long as all other pointers are normalized to the same value and the final
+        // DispatchEntry uses the transform for the normalized pointer.
+        transform =
+                &inputTarget.getTransformForPointer(firstMarkedBit(inputTarget.getPointerIds()));
+        const ui::Transform inverseTransform = transform->inverse();
+        displayTransform = &inputTarget.displayTransform;
 
-    // Iterate through all pointers in the event to normalize against the first.
-    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount(); pointerIndex++) {
-        const PointerProperties& pointerProperties = motionEntry.pointerProperties[pointerIndex];
-        uint32_t pointerId = uint32_t(pointerProperties.id);
-        const ui::Transform& currTransform = inputTarget.pointerTransforms[pointerId];
+        // Iterate through all pointers in the event to normalize against the first.
+        for (size_t i = 0; i < motionEntry.getPointerCount(); i++) {
+            PointerCoords& newCoords = pointerCoords[i];
+            const auto pointerId = motionEntry.pointerProperties[i].id;
+            const ui::Transform& currTransform = inputTarget.getTransformForPointer(pointerId);
 
-        pointerCoords[pointerIndex].copyFrom(motionEntry.pointerCoords[pointerIndex]);
-        // First, apply the current pointer's transform to update the coordinates into
-        // window space.
-        pointerCoords[pointerIndex].transform(currTransform);
-        // Next, apply the inverse transform of the normalized coordinates so the
-        // current coordinates are transformed into the normalized coordinate space.
-        pointerCoords[pointerIndex].transform(inverseFirstTransform);
+            newCoords.copyFrom(motionEntry.pointerCoords[i]);
+            // First, apply the current pointer's transform to update the coordinates into
+            // window space.
+            newCoords.transform(currTransform);
+            // Next, apply the inverse transform of the normalized coordinates so the
+            // current coordinates are transformed into the normalized coordinate space.
+            newCoords.transform(inverseTransform);
+        }
     }
 
     std::unique_ptr<MotionEntry> combinedMotionEntry =
-            std::make_unique<MotionEntry>(motionEntry.id, motionEntry.injectionState,
+            std::make_unique<MotionEntry>(idGenerator.nextId(), motionEntry.injectionState,
                                           motionEntry.eventTime, motionEntry.deviceId,
                                           motionEntry.source, motionEntry.displayId,
                                           motionEntry.policyFlags, motionEntry.action,
@@ -398,23 +463,18 @@
                                           motionEntry.xCursorPosition, motionEntry.yCursorPosition,
                                           motionEntry.downTime, motionEntry.pointerProperties,
                                           pointerCoords);
+    if (tracer) {
+        combinedMotionEntry->traceTracker =
+                tracer->traceDerivedEvent(*combinedMotionEntry, *motionEntry.traceTracker);
+    }
 
     std::unique_ptr<DispatchEntry> dispatchEntry =
             std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
-                                            firstPointerTransform, inputTarget.displayTransform,
-                                            inputTarget.globalScaleFactor);
+                                            *transform, *displayTransform,
+                                            inputTarget.globalScaleFactor, uid, vsyncId, windowId);
     return dispatchEntry;
 }
 
-status_t openInputChannelPair(const std::string& name, std::shared_ptr<InputChannel>& serverChannel,
-                              std::unique_ptr<InputChannel>& clientChannel) {
-    std::unique_ptr<InputChannel> uniqueServerChannel;
-    status_t result = InputChannel::openInputChannelPair(name, uniqueServerChannel, clientChannel);
-
-    serverChannel = std::move(uniqueServerChannel);
-    return result;
-}
-
 template <typename T>
 bool sharedPointersEqual(const std::shared_ptr<T>& lhs, const std::shared_ptr<T>& rhs) {
     if (lhs == nullptr && rhs == nullptr) {
@@ -511,8 +571,8 @@
 }
 
 // Returns true if the given window can accept pointer events at the given display location.
-bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float x, float y,
-                          bool isStylus, const ui::Transform& displayTransform) {
+bool windowAcceptsTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x,
+                          float y, bool isStylus, const ui::Transform& displayTransform) {
     const auto inputConfig = windowInfo.inputConfig;
     if (windowInfo.displayId != displayId ||
         inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
@@ -538,6 +598,18 @@
     return true;
 }
 
+// Returns true if the given window's frame can occlude pointer events at the given display
+// location.
+bool windowOccludesTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x,
+                           float y, const ui::Transform& displayTransform) {
+    if (windowInfo.displayId != displayId) {
+        return false;
+    }
+    const auto frame = displayTransform.transform(windowInfo.frame);
+    const auto p = floor(displayTransform.transform(x, y));
+    return p.x >= frame.left && p.x < frame.right && p.y >= frame.top && p.y < frame.bottom;
+}
+
 bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) {
     return isFromSource(entry.source, AINPUT_SOURCE_STYLUS) &&
             isStylusToolType(entry.pointerProperties[pointerIndex].toolType);
@@ -621,24 +693,24 @@
 std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState,
                                                     const TouchState& newTouchState,
                                                     const MotionEntry& entry) {
-    std::vector<TouchedWindow> out;
     const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
 
     if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
         // ACTION_SCROLL events should not affect the hovering pointer dispatch
         return {};
     }
+    std::vector<TouchedWindow> out;
 
     // We should consider all hovering pointers here. But for now, just use the first one
-    const int32_t pointerId = entry.pointerProperties[0].id;
+    const PointerProperties& pointer = entry.pointerProperties[0];
 
     std::set<sp<WindowInfoHandle>> oldWindows;
     if (oldState != nullptr) {
-        oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+        oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointer.id);
     }
 
     std::set<sp<WindowInfoHandle>> newWindows =
-            newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+            newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointer.id);
 
     // If the pointer is no longer in the new window set, send HOVER_EXIT.
     for (const sp<WindowInfoHandle>& oldWindow : oldWindows) {
@@ -672,7 +744,7 @@
             }
             touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
         }
-        touchedWindow.addHoveringPointer(entry.deviceId, pointerId);
+        touchedWindow.addHoveringPointer(entry.deviceId, pointer);
         if (canReceiveForegroundTouches(*newWindow->getInfo())) {
             touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
         }
@@ -698,7 +770,7 @@
             // TODO(b/282025641): simplify this code once InputTargets are being identified
             // separately from TouchedWindows.
             std::erase_if(targets, [&](const InputTarget& target) {
-                return target.inputChannel->getConnectionToken() == window.windowHandle->getToken();
+                return target.connection->getToken() == window.windowHandle->getToken();
             });
             return true;
         }
@@ -740,25 +812,115 @@
     return true;
 }
 
+/**
+ * Return true if stylus is currently down anywhere on the specified display, and false otherwise.
+ */
+bool isStylusActiveInDisplay(ui::LogicalDisplayId displayId,
+                             const std::unordered_map<ui::LogicalDisplayId /*displayId*/,
+                                                      TouchState>& touchStatesByDisplay) {
+    const auto it = touchStatesByDisplay.find(displayId);
+    if (it == touchStatesByDisplay.end()) {
+        return false;
+    }
+    const TouchState& state = it->second;
+    return state.hasActiveStylus();
+}
+
+Result<void> validateWindowInfosUpdate(const gui::WindowInfosUpdate& update) {
+    struct HashFunction {
+        size_t operator()(const WindowInfo& info) const { return info.id; }
+    };
+
+    std::unordered_set<WindowInfo, HashFunction> windowSet;
+    for (const WindowInfo& info : update.windowInfos) {
+        const auto [_, inserted] = windowSet.insert(info);
+        if (!inserted) {
+            return Error() << "Duplicate entry for " << info;
+        }
+    }
+    return {};
+}
+
+int32_t getUserActivityEventType(const EventEntry& eventEntry) {
+    switch (eventEntry.type) {
+        case EventEntry::Type::KEY: {
+            return USER_ACTIVITY_EVENT_BUTTON;
+        }
+        case EventEntry::Type::MOTION: {
+            const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+            if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) {
+                return USER_ACTIVITY_EVENT_TOUCH;
+            }
+            return USER_ACTIVITY_EVENT_OTHER;
+        }
+        default: {
+            LOG_ALWAYS_FATAL("%s events are not user activity",
+                             ftl::enum_string(eventEntry.type).c_str());
+        }
+    }
+}
+
+std::pair<bool /*cancelPointers*/, bool /*cancelNonPointers*/> expandCancellationMode(
+        CancelationOptions::Mode mode) {
+    switch (mode) {
+        case CancelationOptions::Mode::CANCEL_ALL_EVENTS:
+            return {true, true};
+        case CancelationOptions::Mode::CANCEL_POINTER_EVENTS:
+            return {true, false};
+        case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
+            return {false, true};
+        case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS:
+            return {false, true};
+    }
+}
+
+class ScopedSyntheticEventTracer {
+public:
+    ScopedSyntheticEventTracer(std::unique_ptr<trace::InputTracerInterface>& tracer)
+          : mTracer(tracer) {
+        if (mTracer) {
+            mEventTracker = mTracer->createTrackerForSyntheticEvent();
+        }
+    }
+
+    ~ScopedSyntheticEventTracer() {
+        if (mTracer) {
+            mTracer->eventProcessingComplete(*mEventTracker);
+        }
+    }
+
+    const std::unique_ptr<trace::EventTrackerInterface>& getTracker() const {
+        return mEventTracker;
+    }
+
+private:
+    std::unique_ptr<trace::InputTracerInterface>& mTracer;
+    std::unique_ptr<trace::EventTrackerInterface> mEventTracker;
+};
+
 } // namespace
 
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
+      : InputDispatcher(policy, createInputTracingBackendIfEnabled()) {}
+
+InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
+                                 std::unique_ptr<trace::InputTracingBackendInterface> traceBackend)
       : mPolicy(policy),
         mPendingEvent(nullptr),
         mLastDropReason(DropReason::NOT_DROPPED),
         mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
-        mAppSwitchSawKeyDown(false),
-        mAppSwitchDueTime(LLONG_MAX),
+        mMinTimeBetweenUserActivityPokes(DEFAULT_USER_ACTIVITY_POKE_INTERVAL),
         mNextUnblockedEvent(nullptr),
         mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT),
         mDispatchEnabled(false),
         mDispatchFrozen(false),
         mInputFilterEnabled(false),
         mMaximumObscuringOpacityForTouch(1.0f),
-        mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
+        mFocusedDisplayId(ui::LogicalDisplayId::DEFAULT),
         mWindowTokenWithPointerCapture(nullptr),
+        mAwaitedApplicationDisplayId(ui::LogicalDisplayId::INVALID),
         mLatencyAggregator(),
         mLatencyTracker(&mLatencyAggregator) {
     mLooper = sp<Looper>::make(false);
@@ -769,6 +931,12 @@
     SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener);
 #endif
     mKeyRepeatState.lastKeyEntry = nullptr;
+
+    if (traceBackend) {
+        mTracer = std::make_unique<trace::impl::InputTracer>(std::move(traceBackend));
+    }
+
+    mLastUserActivityTimes.fill(0);
 }
 
 InputDispatcher::~InputDispatcher() {
@@ -781,7 +949,7 @@
 
     while (!mConnectionsByToken.empty()) {
         std::shared_ptr<Connection> connection = mConnectionsByToken.begin()->second;
-        removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false);
+        removeInputChannelLocked(connection->getToken(), /*notify=*/false);
     }
 }
 
@@ -812,7 +980,7 @@
         // Run a dispatch loop if there are no pending commands.
         // The dispatch loop might enqueue commands to run afterwards.
         if (!haveCommandsLocked()) {
-            dispatchOnceInnerLocked(&nextWakeupTime);
+            dispatchOnceInnerLocked(/*byref*/ nextWakeupTime);
         }
 
         // Run all pending commands if there are any.
@@ -901,7 +1069,7 @@
     }
     connection->responsive = false;
     // Stop waking up for this unresponsive connection
-    mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
+    mAnrTracker.eraseToken(connection->getToken());
     onAnrLocked(connection);
     return LLONG_MIN;
 }
@@ -911,15 +1079,14 @@
     if (connection->monitor) {
         return mMonitorDispatchingTimeout;
     }
-    const sp<WindowInfoHandle> window =
-            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
+    const sp<WindowInfoHandle> window = getWindowHandleLocked(connection->getToken());
     if (window != nullptr) {
         return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
     }
     return DEFAULT_INPUT_DISPATCHING_TIMEOUT;
 }
 
-void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) {
     nsecs_t currentTime = now();
 
     // Reset the key repeat timer whenever normal dispatch is suspended while the
@@ -937,38 +1104,16 @@
         return;
     }
 
-    // Optimize latency of app switches.
-    // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
-    // been pressed.  When it expires, we preempt dispatch and drop all other pending events.
-    bool isAppSwitchDue;
-    if (!REMOVE_APP_SWITCH_DROPS) {
-        isAppSwitchDue = mAppSwitchDueTime <= currentTime;
-        if (mAppSwitchDueTime < *nextWakeupTime) {
-            *nextWakeupTime = mAppSwitchDueTime;
-        }
-    }
-
     // Ready to start a new event.
     // If we don't already have a pending event, go grab one.
     if (!mPendingEvent) {
         if (mInboundQueue.empty()) {
-            if (!REMOVE_APP_SWITCH_DROPS) {
-                if (isAppSwitchDue) {
-                    // The inbound queue is empty so the app switch key we were waiting
-                    // for will never arrive.  Stop waiting for it.
-                    resetPendingAppSwitchLocked(false);
-                    isAppSwitchDue = false;
-                }
-            }
-
             // Synthesize a key repeat if appropriate.
             if (mKeyRepeatState.lastKeyEntry) {
                 if (currentTime >= mKeyRepeatState.nextRepeatTime) {
                     mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
                 } else {
-                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
-                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
-                    }
+                    nextWakeupTime = std::min(nextWakeupTime, mKeyRepeatState.nextRepeatTime);
                 }
             }
 
@@ -1057,16 +1202,6 @@
         case EventEntry::Type::KEY: {
             std::shared_ptr<const KeyEntry> keyEntry =
                     std::static_pointer_cast<const KeyEntry>(mPendingEvent);
-            if (!REMOVE_APP_SWITCH_DROPS) {
-                if (isAppSwitchDue) {
-                    if (isAppSwitchKeyEvent(*keyEntry)) {
-                        resetPendingAppSwitchLocked(true);
-                        isAppSwitchDue = false;
-                    } else if (dropReason == DropReason::NOT_DROPPED) {
-                        dropReason = DropReason::APP_SWITCH;
-                    }
-                }
-            }
             if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *keyEntry)) {
                 dropReason = DropReason::STALE;
             }
@@ -1080,11 +1215,6 @@
         case EventEntry::Type::MOTION: {
             std::shared_ptr<const MotionEntry> motionEntry =
                     std::static_pointer_cast<const MotionEntry>(mPendingEvent);
-            if (!REMOVE_APP_SWITCH_DROPS) {
-                if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
-                    dropReason = DropReason::APP_SWITCH;
-                }
-            }
             if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
                 // The event is stale. However, only drop stale events if there isn't an ongoing
                 // gesture. That would allow us to complete the processing of the current stroke.
@@ -1110,11 +1240,7 @@
         case EventEntry::Type::SENSOR: {
             std::shared_ptr<const SensorEntry> sensorEntry =
                     std::static_pointer_cast<const SensorEntry>(mPendingEvent);
-            if (!REMOVE_APP_SWITCH_DROPS) {
-                if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
-                    dropReason = DropReason::APP_SWITCH;
-                }
-            }
+
             //  Sensor timestamps use SYSTEM_TIME_BOOTTIME time base, so we can't use
             // 'currentTime' here, get SYSTEM_TIME_BOOTTIME instead.
             nsecs_t bootTime = systemTime(SYSTEM_TIME_BOOTTIME);
@@ -1133,8 +1259,14 @@
         }
         mLastDropReason = dropReason;
 
+        if (mTracer) {
+            if (auto& traceTracker = getTraceTracker(*mPendingEvent); traceTracker != nullptr) {
+                mTracer->eventProcessingComplete(*traceTracker);
+            }
+        }
+
         releasePendingEventLocked();
-        *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
+        nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
     }
 }
 
@@ -1146,7 +1278,7 @@
  * Return true if the events preceding this incoming motion event should be dropped
  * Return false otherwise (the default behaviour)
  */
-bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) {
+bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) const {
     const bool isPointerDownEvent = motionEntry.action == AMOTION_EVENT_ACTION_DOWN &&
             isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER);
 
@@ -1155,7 +1287,7 @@
     // If the application takes too long to catch up then we drop all events preceding
     // the touch into the other window.
     if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) {
-        const int32_t displayId = motionEntry.displayId;
+        const ui::LogicalDisplayId displayId = motionEntry.displayId;
         const auto [x, y] = resolveTouchedPosition(motionEntry);
         const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0);
 
@@ -1188,16 +1320,6 @@
         }
     }
 
-    // Prevent getting stuck: if we have a pending key event, and some motion events that have not
-    // yet been processed by some connections, the dispatcher will wait for these motion
-    // events to be processed before dispatching the key event. This is because these motion events
-    // may cause a new window to be launched, which the user might expect to receive focus.
-    // To prevent waiting forever for such events, just send the key to the currently focused window
-    if (isPointerDownEvent && mKeyIsWaitingForEventsTimeout) {
-        ALOGD("Received a new pointer down event, stop waiting for events to process and "
-              "just send the pending key event to the focused window.");
-        mKeyIsWaitingForEventsTimeout = now();
-    }
     return false;
 }
 
@@ -1211,27 +1333,12 @@
         case EventEntry::Type::KEY: {
             LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0,
                                 "Unexpected untrusted event.");
-            // Optimize app switch latency.
-            // If the application takes too long to catch up then we drop all events preceding
-            // the app switch key.
-            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
 
-            if (!REMOVE_APP_SWITCH_DROPS) {
-                if (isAppSwitchKeyEvent(keyEntry)) {
-                    if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) {
-                        mAppSwitchSawKeyDown = true;
-                    } else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
-                        if (mAppSwitchSawKeyDown) {
-                            if (DEBUG_APP_SWITCH) {
-                                ALOGD("App switch is pending!");
-                            }
-                            mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;
-                            mAppSwitchSawKeyDown = false;
-                            needWake = true;
-                        }
-                    }
-                }
+            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
+            if (mTracer) {
+                ensureEventTraced(keyEntry);
             }
+
             // If a new up event comes in, and the pending event with same key code has been asked
             // to try again later because of the policy. We have to reset the intercept key wake up
             // time for it may have been handled in the policy and could be dropped.
@@ -1252,10 +1359,29 @@
         case EventEntry::Type::MOTION: {
             LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0,
                                 "Unexpected untrusted event.");
-            if (shouldPruneInboundQueueLocked(static_cast<const MotionEntry&>(entry))) {
+            const auto& motionEntry = static_cast<const MotionEntry&>(entry);
+            if (mTracer) {
+                ensureEventTraced(motionEntry);
+            }
+            if (shouldPruneInboundQueueLocked(motionEntry)) {
                 mNextUnblockedEvent = mInboundQueue.back();
                 needWake = true;
             }
+
+            const bool isPointerDownEvent = motionEntry.action == AMOTION_EVENT_ACTION_DOWN &&
+                    isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER);
+            if (isPointerDownEvent && mKeyIsWaitingForEventsTimeout) {
+                // Prevent waiting too long for unprocessed events: if we have a pending key event,
+                // and some other events have not yet been processed, the dispatcher will wait for
+                // these events to be processed before dispatching the key event. This is because
+                // the unprocessed events may cause the focus to change (for example, by launching a
+                // new window or tapping a different window). To prevent waiting too long, we force
+                // the key to be sent to the currently focused window when a new tap comes in.
+                ALOGD("Received a new pointer down event, stop waiting for events to process and "
+                      "just send the pending key event to the currently focused window.");
+                mKeyIsWaitingForEventsTimeout = now();
+                needWake = true;
+            }
             break;
         }
         case EventEntry::Type::FOCUS: {
@@ -1286,8 +1412,8 @@
     }
 }
 
-sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y,
-                                                                bool isStylus,
+sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(ui::LogicalDisplayId displayId,
+                                                                float x, float y, bool isStylus,
                                                                 bool ignoreDragWindow) const {
     // Traverse windows from front to back to find touched window.
     const auto& windowHandles = getWindowHandlesLocked(displayId);
@@ -1306,7 +1432,8 @@
 }
 
 std::vector<InputTarget> InputDispatcher::findOutsideTargetsLocked(
-        int32_t displayId, const sp<WindowInfoHandle>& touchedWindow, int32_t pointerId) const {
+        ui::LogicalDisplayId displayId, const sp<WindowInfoHandle>& touchedWindow,
+        int32_t pointerId) const {
     if (touchedWindow == nullptr) {
         return {};
     }
@@ -1333,7 +1460,7 @@
 }
 
 std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
-        int32_t displayId, float x, float y, bool isStylus) const {
+        ui::LogicalDisplayId displayId, float x, float y, bool isStylus) const {
     // Traverse windows from front to back and gather the touched spy windows.
     std::vector<sp<WindowInfoHandle>> spyWindows;
     const auto& windowHandles = getWindowHandlesLocked(displayId);
@@ -1367,13 +1494,10 @@
             }
             reason = "inbound event was dropped because input dispatch is disabled";
             break;
-        case DropReason::APP_SWITCH:
-            ALOGI("Dropped event because of pending overdue app switch.");
-            reason = "inbound event was dropped because of pending overdue app switch";
-            break;
         case DropReason::BLOCKED:
-            ALOGI("Dropped event because the current application is not responding and the user "
-                  "has started interacting with a different application.");
+            LOG(INFO) << "Dropping because the current application is not responding and the user "
+                         "has started interacting with a different application: "
+                      << entry.getDescription();
             reason = "inbound event was dropped because the current application is not responding "
                      "and the user has started interacting with a different application";
             break;
@@ -1393,8 +1517,9 @@
 
     switch (entry.type) {
         case EventEntry::Type::KEY: {
-            CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason);
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
+            CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason,
+                                       keyEntry.traceTracker);
             options.displayId = keyEntry.displayId;
             options.deviceId = keyEntry.deviceId;
             synthesizeCancelationEventsForAllConnectionsLocked(options);
@@ -1403,13 +1528,14 @@
         case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
             if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) {
-                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason);
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason,
+                                           motionEntry.traceTracker);
                 options.displayId = motionEntry.displayId;
                 options.deviceId = motionEntry.deviceId;
                 synthesizeCancelationEventsForAllConnectionsLocked(options);
             } else {
                 CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
-                                           reason);
+                                           reason, motionEntry.traceTracker);
                 options.displayId = motionEntry.displayId;
                 options.deviceId = motionEntry.deviceId;
                 synthesizeCancelationEventsForAllConnectionsLocked(options);
@@ -1433,33 +1559,6 @@
     }
 }
 
-static bool isAppSwitchKeyCode(int32_t keyCode) {
-    return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL ||
-            keyCode == AKEYCODE_APP_SWITCH;
-}
-
-bool InputDispatcher::isAppSwitchKeyEvent(const KeyEntry& keyEntry) {
-    return !(keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) && isAppSwitchKeyCode(keyEntry.keyCode) &&
-            (keyEntry.policyFlags & POLICY_FLAG_TRUSTED) &&
-            (keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER);
-}
-
-bool InputDispatcher::isAppSwitchPendingLocked() const {
-    return mAppSwitchDueTime != LLONG_MAX;
-}
-
-void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
-    mAppSwitchDueTime = LLONG_MAX;
-
-    if (DEBUG_APP_SWITCH) {
-        if (handled) {
-            ALOGD("App switch has arrived.");
-        } else {
-            ALOGD("App switch was abandoned.");
-        }
-    }
-}
-
 bool InputDispatcher::haveCommandsLocked() const {
     return !mCommandQueue.empty();
 }
@@ -1532,6 +1631,10 @@
                                        entry->repeatCount + 1, entry->downTime);
 
     newEntry->syntheticRepeat = true;
+    if (mTracer) {
+        newEntry->traceTracker = mTracer->traceInboundEvent(*newEntry);
+    }
+
     mKeyRepeatState.lastKeyEntry = newEntry;
     mKeyRepeatState.nextRepeatTime = currentTime + mConfig.keyRepeatDelay;
     return newEntry;
@@ -1567,7 +1670,9 @@
         resetKeyRepeatLocked();
     }
 
-    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset");
+    ScopedSyntheticEventTracer traceContext(mTracer);
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset",
+                               traceContext.getTracker());
     options.deviceId = entry.deviceId;
     synthesizeCancelationEventsForAllConnectionsLocked(options);
 
@@ -1604,18 +1709,16 @@
 
 void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime,
                                           std::shared_ptr<const FocusEntry> entry) {
-    std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
-    if (channel == nullptr) {
-        return; // Window has gone away
+    std::shared_ptr<Connection> connection = getConnectionLocked(entry->connectionToken);
+    if (connection == nullptr) {
+        return; // Connection has gone away
     }
-    InputTarget target;
-    target.inputChannel = channel;
     entry->dispatchInProgress = true;
     std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") +
-            channel->getName();
+            connection->getInputChannelName();
     std::string reason = std::string("reason=").append(entry->reason);
     android_log_event_list(LOGTAG_INPUT_FOCUS) << message << reason << LOG_ID_EVENTS;
-    dispatchEventLocked(currentTime, entry, {target});
+    dispatchEventLocked(currentTime, entry, {{connection}});
 }
 
 void InputDispatcher::dispatchPointerCaptureChangedLocked(
@@ -1626,7 +1729,7 @@
     const bool haveWindowWithPointerCapture = mWindowTokenWithPointerCapture != nullptr;
     sp<IBinder> token;
 
-    if (entry->pointerCaptureRequest.enable) {
+    if (entry->pointerCaptureRequest.isEnable()) {
         // Enable Pointer Capture.
         if (haveWindowWithPointerCapture &&
             (entry->pointerCaptureRequest == mCurrentPointerCaptureRequest)) {
@@ -1635,7 +1738,7 @@
             ALOGI("Skipping dispatch of Pointer Capture being enabled: no state change.");
             return;
         }
-        if (!mCurrentPointerCaptureRequest.enable) {
+        if (!mCurrentPointerCaptureRequest.isEnable()) {
             // This can happen if a window requests capture and immediately releases capture.
             ALOGW("No window requested Pointer Capture.");
             dropReason = DropReason::NO_POINTER_CAPTURE;
@@ -1648,6 +1751,8 @@
 
         token = mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
         LOG_ALWAYS_FATAL_IF(!token, "Cannot find focused window for Pointer Capture.");
+        LOG_ALWAYS_FATAL_IF(token != entry->pointerCaptureRequest.window,
+                            "Unexpected requested window for Pointer Capture.");
         mWindowTokenWithPointerCapture = token;
     } else {
         // Disable Pointer Capture.
@@ -1667,24 +1772,22 @@
         }
         token = mWindowTokenWithPointerCapture;
         mWindowTokenWithPointerCapture = nullptr;
-        if (mCurrentPointerCaptureRequest.enable) {
-            setPointerCaptureLocked(false);
+        if (mCurrentPointerCaptureRequest.isEnable()) {
+            setPointerCaptureLocked(nullptr);
         }
     }
 
-    auto channel = getInputChannelLocked(token);
-    if (channel == nullptr) {
+    auto connection = getConnectionLocked(token);
+    if (connection == nullptr) {
         // Window has gone away, clean up Pointer Capture state.
         mWindowTokenWithPointerCapture = nullptr;
-        if (mCurrentPointerCaptureRequest.enable) {
-            setPointerCaptureLocked(false);
+        if (mCurrentPointerCaptureRequest.isEnable()) {
+            setPointerCaptureLocked(nullptr);
         }
         return;
     }
-    InputTarget target;
-    target.inputChannel = channel;
     entry->dispatchInProgress = true;
-    dispatchEventLocked(currentTime, entry, {target});
+    dispatchEventLocked(currentTime, entry, {{connection}});
 
     dropReason = DropReason::NOT_DROPPED;
 }
@@ -1713,22 +1816,20 @@
         if (token == nullptr) {
             continue;
         }
-        std::shared_ptr<InputChannel> channel = getInputChannelLocked(token);
-        if (channel == nullptr) {
-            continue; // Window has gone away
+        std::shared_ptr<Connection> connection = getConnectionLocked(token);
+        if (connection == nullptr) {
+            continue; // Connection has gone away
         }
-        InputTarget target;
-        target.inputChannel = channel;
-        inputTargets.push_back(target);
+        inputTargets.emplace_back(connection);
     }
     return inputTargets;
 }
 
 bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
-                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
+                                        DropReason* dropReason, nsecs_t& nextWakeupTime) {
     // Preprocessing.
     if (!entry->dispatchInProgress) {
-        if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN &&
+        if (!entry->syntheticRepeat && entry->action == AKEY_EVENT_ACTION_DOWN &&
             (entry->policyFlags & POLICY_FLAG_TRUSTED) &&
             (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) {
             if (mKeyRepeatState.lastKeyEntry &&
@@ -1776,9 +1877,7 @@
     // Handle case where the policy asked us to try again later last time.
     if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
         if (currentTime < entry->interceptKeyWakeupTime) {
-            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
-                *nextWakeupTime = entry->interceptKeyWakeupTime;
-            }
+            nextWakeupTime = std::min(nextWakeupTime, entry->interceptKeyWakeupTime);
             return false; // wait until next wakeup
         }
         entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
@@ -1840,6 +1939,13 @@
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
 
+    if (mTracer) {
+        ensureEventTraced(*entry);
+        for (const auto& target : inputTargets) {
+            mTracer->dispatchToTargetHint(*entry->traceTracker, target);
+        }
+    }
+
     // Dispatch the key.
     dispatchEventLocked(currentTime, entry, inputTargets);
     return true;
@@ -1847,18 +1953,18 @@
 
 void InputDispatcher::logOutboundKeyDetails(const char* prefix, const KeyEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32 ", "
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%s, "
               "policyFlags=0x%x, action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, "
               "metaState=0x%x, repeatCount=%d, downTime=%" PRId64,
-              prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
-              entry.policyFlags, entry.action, entry.flags, entry.keyCode, entry.scanCode,
-              entry.metaState, entry.repeatCount, entry.downTime);
+              prefix, entry.eventTime, entry.deviceId, entry.source,
+              entry.displayId.toString().c_str(), entry.policyFlags, entry.action, entry.flags,
+              entry.keyCode, entry.scanCode, entry.metaState, entry.repeatCount, entry.downTime);
     }
 }
 
 void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime,
                                            const std::shared_ptr<const SensorEntry>& entry,
-                                           DropReason* dropReason, nsecs_t* nextWakeupTime) {
+                                           DropReason* dropReason, nsecs_t& nextWakeupTime) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
               "source=0x%x, sensorType=%s",
@@ -1898,7 +2004,7 @@
 
 bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime,
                                            std::shared_ptr<const MotionEntry> entry,
-                                           DropReason* dropReason, nsecs_t* nextWakeupTime) {
+                                           DropReason* dropReason, nsecs_t& nextWakeupTime) {
     ATRACE_CALL();
     // Preprocessing.
     if (!entry->dispatchInProgress) {
@@ -1957,7 +2063,8 @@
         CancelationOptions::Mode mode(
                 isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS
                                : CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS);
-        CancelationOptions options(mode, "input event injection failed");
+        CancelationOptions options(mode, "input event injection failed", entry->traceTracker);
+        options.displayId = entry->displayId;
         synthesizeCancelationEventsForMonitorsLocked(options);
         return true;
     }
@@ -1965,6 +2072,13 @@
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
 
+    if (mTracer) {
+        ensureEventTraced(*entry);
+        for (const auto& target : inputTargets) {
+            mTracer->dispatchToTargetHint(*entry->traceTracker, target);
+        }
+    }
+
     // Dispatch the motion.
     dispatchEventLocked(currentTime, entry, inputTargets);
     return true;
@@ -1983,28 +2097,25 @@
 
 void InputDispatcher::dispatchDragLocked(nsecs_t currentTime,
                                          std::shared_ptr<const DragEntry> entry) {
-    std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
-    if (channel == nullptr) {
-        return; // Window has gone away
+    std::shared_ptr<Connection> connection = getConnectionLocked(entry->connectionToken);
+    if (connection == nullptr) {
+        return; // Connection has gone away
     }
-    InputTarget target;
-    target.inputChannel = channel;
     entry->dispatchInProgress = true;
-    dispatchEventLocked(currentTime, entry, {target});
+    dispatchEventLocked(currentTime, entry, {{connection}});
 }
 
 void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32
-              ", policyFlags=0x%x, "
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%s, policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, "
               "metaState=0x%x, buttonState=0x%x,"
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
               prefix, entry.eventTime, entry.deviceId,
-              inputEventSourceToString(entry.source).c_str(), entry.displayId, entry.policyFlags,
-              MotionEvent::actionToString(entry.action).c_str(), entry.actionButton, entry.flags,
-              entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
-              entry.yPrecision, entry.downTime);
+              inputEventSourceToString(entry.source).c_str(), entry.displayId.toString().c_str(),
+              entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
+              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
+              entry.xPrecision, entry.yPrecision, entry.downTime);
 
         for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%s, "
@@ -2041,17 +2152,8 @@
     pokeUserActivityLocked(*eventEntry);
 
     for (const InputTarget& inputTarget : inputTargets) {
-        std::shared_ptr<Connection> connection =
-                getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
-        if (connection != nullptr) {
-            prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
-        } else {
-            if (DEBUG_DROPPED_EVENTS_VERBOSE) {
-                LOG(INFO) << "Dropping event delivery to target with channel "
-                          << inputTarget.inputChannel->getName()
-                          << " because it is no longer registered with the input dispatcher.";
-            }
-        }
+        std::shared_ptr<Connection> connection = inputTarget.connection;
+        prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
     }
 }
 
@@ -2062,12 +2164,25 @@
     // sending new pointers to the connection when it blocked, but focused events will continue to
     // pile up.
     ALOGW("Canceling events for %s because it is unresponsive",
-          connection->inputChannel->getName().c_str());
-    if (connection->status == Connection::Status::NORMAL) {
-        CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS,
-                                   "application not responding");
-        synthesizeCancelationEventsForConnectionLocked(connection, options);
+          connection->getInputChannelName().c_str());
+    if (connection->status != Connection::Status::NORMAL) {
+        return;
     }
+    ScopedSyntheticEventTracer traceContext(mTracer);
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS,
+                               "application not responding", traceContext.getTracker());
+
+    sp<WindowInfoHandle> windowHandle;
+    if (!connection->monitor) {
+        windowHandle = getWindowHandleLocked(connection->getToken());
+        if (windowHandle == nullptr) {
+            // The window that is receiving this ANR was removed, so there is no need to generate
+            // cancellations, because the cancellations would have already been generated when
+            // the window was removed.
+            return;
+        }
+    }
+    synthesizeCancelationEventsForConnectionLocked(connection, options, windowHandle);
 }
 
 void InputDispatcher::resetNoFocusedWindowTimeoutLocked() {
@@ -2085,8 +2200,8 @@
  * then it should be dispatched to that display. Otherwise, the event goes to the focused display.
  * Focused display is the display that the user most recently interacted with.
  */
-int32_t InputDispatcher::getTargetDisplayId(const EventEntry& entry) {
-    int32_t displayId;
+ui::LogicalDisplayId InputDispatcher::getTargetDisplayId(const EventEntry& entry) {
+    ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
     switch (entry.type) {
         case EventEntry::Type::KEY: {
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
@@ -2106,10 +2221,10 @@
         case EventEntry::Type::SENSOR:
         case EventEntry::Type::DRAG: {
             ALOGE("%s events do not have a target display", ftl::enum_string(entry.type).c_str());
-            return ADISPLAY_ID_NONE;
+            return ui::LogicalDisplayId::INVALID;
         }
     }
-    return displayId == ADISPLAY_ID_NONE ? mFocusedDisplayId : displayId;
+    return displayId == ui::LogicalDisplayId::INVALID ? mFocusedDisplayId : displayId;
 }
 
 bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime,
@@ -2124,7 +2239,8 @@
         // Start the timer
         // Wait to send key because there are unprocessed events that may cause focus to change
         mKeyIsWaitingForEventsTimeout = currentTime +
-                std::chrono::duration_cast<std::chrono::nanoseconds>(KEY_WAITING_FOR_EVENTS_TIMEOUT)
+                std::chrono::duration_cast<std::chrono::nanoseconds>(
+                        mPolicy.getKeyWaitingForEventsTimeout())
                         .count();
         return true;
     }
@@ -2143,11 +2259,11 @@
 }
 
 sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
-        nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+        nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
         InputEventInjectionResult& outInjectionResult) {
     outInjectionResult = InputEventInjectionResult::FAILED; // Default result
 
-    int32_t displayId = getTargetDisplayId(entry);
+    ui::LogicalDisplayId displayId = getTargetDisplayId(entry);
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
     std::shared_ptr<InputApplicationHandle> focusedApplicationHandle =
             getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
@@ -2156,8 +2272,8 @@
     // then drop the event.
     if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
         ALOGI("Dropping %s event because there is no focused window or focused application in "
-              "display %" PRId32 ".",
-              ftl::enum_string(entry.type).c_str(), displayId);
+              "display %s.",
+              ftl::enum_string(entry.type).c_str(), displayId.toString().c_str());
         return nullptr;
     }
 
@@ -2182,7 +2298,7 @@
             ALOGW("Waiting because no window has focus but %s may eventually add a "
                   "window when it finishes starting up. Will wait for %" PRId64 "ms",
                   mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
-            *nextWakeupTime = *mNoFocusedWindowTimeoutTime;
+            nextWakeupTime = std::min(nextWakeupTime, *mNoFocusedWindowTimeoutTime);
             outInjectionResult = InputEventInjectionResult::PENDING;
             return nullptr;
         } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
@@ -2227,7 +2343,7 @@
     // prior input events.
     if (entry.type == EventEntry::Type::KEY) {
         if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
-            *nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
+            nextWakeupTime = std::min(nextWakeupTime, *mKeyIsWaitingForEventsTimeout);
             outInjectionResult = InputEventInjectionResult::PENDING;
             return nullptr;
         }
@@ -2245,17 +2361,11 @@
         const std::vector<Monitor>& monitors) const {
     std::vector<Monitor> responsiveMonitors;
     std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors),
-                 [this](const Monitor& monitor) REQUIRES(mLock) {
-                     std::shared_ptr<Connection> connection =
-                             getConnectionLocked(monitor.inputChannel->getConnectionToken());
-                     if (connection == nullptr) {
-                         ALOGE("Could not find connection for monitor %s",
-                               monitor.inputChannel->getName().c_str());
-                         return false;
-                     }
+                 [](const Monitor& monitor) REQUIRES(mLock) {
+                     std::shared_ptr<Connection> connection = monitor.connection;
                      if (!connection->responsive) {
                          ALOGW("Unresponsive monitor %s will not get the new gesture",
-                               connection->inputChannel->getName().c_str());
+                               connection->getInputChannelName().c_str());
                          return false;
                      }
                      return true;
@@ -2271,7 +2381,7 @@
     std::vector<InputTarget> targets;
     // For security reasons, we defer updating the touch state until we are sure that
     // event injection will be allowed.
-    const int32_t displayId = entry.displayId;
+    const ui::LogicalDisplayId displayId = entry.displayId;
     const int32_t action = entry.action;
     const int32_t maskedAction = MotionEvent::getActionMasked(action);
 
@@ -2329,7 +2439,7 @@
         /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
         const auto [x, y] = resolveTouchedPosition(entry);
         const int32_t pointerIndex = MotionEvent::getActionIndex(action);
-        const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
+        const PointerProperties& pointer = entry.pointerProperties[pointerIndex];
         // Outside targets should be added upon first dispatched DOWN event. That means, this should
         // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
         const bool isStylus = isPointerFromStylus(entry, pointerIndex);
@@ -2337,13 +2447,14 @@
                 findTouchedWindowAtLocked(displayId, x, y, isStylus);
 
         if (isDown) {
-            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointerId);
+            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointer.id);
         }
         // Handle the case where we did not find a window.
         if (newTouchedWindowHandle == nullptr) {
-            ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId);
+            ALOGD("No new touched window at (%.1f, %.1f) in display %s", x, y,
+                  displayId.toString().c_str());
             // Try to assign the pointer to the first foreground window we find, if there is one.
-            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
+            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
         }
 
         // Verify targeted injection.
@@ -2382,8 +2493,8 @@
 
         if (newTouchedWindows.empty()) {
             ALOGI("Dropping event because there is no touchable window at (%.1f, %.1f) on display "
-                  "%d.",
-                  x, y, displayId);
+                  "%s.",
+                  x, y, displayId.toString().c_str());
             outInjectionResult = InputEventInjectionResult::FAILED;
             return {};
         }
@@ -2395,7 +2506,7 @@
 
             if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
-                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
+                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer);
             }
 
             // Set target flags.
@@ -2418,15 +2529,21 @@
             // Update the temporary touch state.
 
             if (!isHoverAction) {
-                std::bitset<MAX_POINTER_ID + 1> pointerIds;
-                pointerIds.set(pointerId);
                 const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                         maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
-                tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS,
-                                                 targetFlags, entry.deviceId, pointerIds,
-                                                 isDownOrPointerDown
-                                                         ? std::make_optional(entry.eventTime)
-                                                         : std::nullopt);
+                Result<void> addResult =
+                        tempTouchState.addOrUpdateWindow(windowHandle,
+                                                         InputTarget::DispatchMode::AS_IS,
+                                                         targetFlags, entry.deviceId, {pointer},
+                                                         isDownOrPointerDown
+                                                                 ? std::make_optional(
+                                                                           entry.eventTime)
+                                                                 : std::nullopt);
+                if (!addResult.ok()) {
+                    LOG(ERROR) << "Error while processing " << entry << " for "
+                               << windowHandle->getName();
+                    logDispatchStateLocked();
+                }
                 // If this is the pointer going down and the touched window has a wallpaper
                 // then also add the touched wallpaper windows so they are locked in for the
                 // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or
@@ -2446,7 +2563,7 @@
                         }
                         tempTouchState.addOrUpdateWindow(wallpaper,
                                                          InputTarget::DispatchMode::AS_IS,
-                                                         wallpaperFlags, entry.deviceId, pointerIds,
+                                                         wallpaperFlags, entry.deviceId, {pointer},
                                                          entry.eventTime);
                     }
                 }
@@ -2457,12 +2574,12 @@
         // make it pilfering. This will prevent other non-spy windows from getting this pointer,
         // which is a specific behaviour that we want.
         for (TouchedWindow& touchedWindow : tempTouchState.windows) {
-            if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
+            if (touchedWindow.hasTouchingPointer(entry.deviceId, pointer.id) &&
                 touchedWindow.hasPilferingPointers(entry.deviceId)) {
                 // This window is already pilfering some pointers, and this new pointer is also
                 // going to it. Therefore, take over this pointer and don't give it to anyone
                 // else.
-                touchedWindow.addPilferingPointer(entry.deviceId, pointerId);
+                touchedWindow.addPilferingPointer(entry.deviceId, pointer.id);
             }
         }
 
@@ -2501,11 +2618,11 @@
 
         // Check whether touches should slip outside of the current foreground window.
         if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.getPointerCount() == 1 &&
-            tempTouchState.isSlippery()) {
+            tempTouchState.isSlippery(entry.deviceId)) {
             const auto [x, y] = resolveTouchedPosition(entry);
             const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
-                    tempTouchState.getFirstForegroundWindowHandle();
+                    tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
             LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
             sp<WindowInfoHandle> newTouchedWindowHandle =
                     findTouchedWindowAtLocked(displayId, x, y, isStylus);
@@ -2517,22 +2634,22 @@
                 return {};
             }
 
-            // Drop touch events if requested by input feature
+            // Do not slide events to the window which can not receive motion event
             if (newTouchedWindowHandle != nullptr &&
-                shouldDropInput(entry, newTouchedWindowHandle)) {
+                !canWindowReceiveMotionLocked(newTouchedWindowHandle, entry)) {
                 newTouchedWindowHandle = nullptr;
             }
 
             if (newTouchedWindowHandle != nullptr &&
                 !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) {
-                ALOGI("Touch is slipping out of window %s into window %s in display %" PRId32,
+                ALOGI("Touch is slipping out of window %s into window %s in display %s",
                       oldTouchedWindowHandle->getName().c_str(),
-                      newTouchedWindowHandle->getName().c_str(), displayId);
+                      newTouchedWindowHandle->getName().c_str(), displayId.toString().c_str());
 
                 // Make a slippery exit from the old window.
                 std::bitset<MAX_POINTER_ID + 1> pointerIds;
-                const int32_t pointerId = entry.pointerProperties[0].id;
-                pointerIds.set(pointerId);
+                const PointerProperties& pointer = entry.pointerProperties[0];
+                pointerIds.set(pointer.id);
 
                 const TouchedWindow& touchedWindow =
                         tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
@@ -2562,13 +2679,13 @@
 
                 tempTouchState.addOrUpdateWindow(newTouchedWindowHandle,
                                                  InputTarget::DispatchMode::SLIPPERY_ENTER,
-                                                 targetFlags, entry.deviceId, pointerIds,
+                                                 targetFlags, entry.deviceId, {pointer},
                                                  entry.eventTime);
 
                 // Check if the wallpaper window should deliver the corresponding event.
                 slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
-                                   tempTouchState, entry.deviceId, pointerId, targets);
-                tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointerId,
+                                   tempTouchState, entry.deviceId, pointer, targets);
+                tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointer.id,
                                                                oldTouchedWindowHandle);
             }
         }
@@ -2577,14 +2694,12 @@
         if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
             // If no split, we suppose all touched windows should receive pointer down.
             const int32_t pointerIndex = MotionEvent::getActionIndex(action);
-            for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
-                TouchedWindow& touchedWindow = tempTouchState.windows[i];
+            std::vector<PointerProperties> touchingPointers{entry.pointerProperties[pointerIndex]};
+            for (TouchedWindow& touchedWindow : tempTouchState.windows) {
                 // Ignore drag window for it should just track one pointer.
                 if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
                     continue;
                 }
-                std::bitset<MAX_POINTER_ID + 1> touchingPointers;
-                touchingPointers.set(entry.pointerProperties[pointerIndex].id);
                 touchedWindow.addTouchingPointers(entry.deviceId, touchingPointers);
             }
         }
@@ -2594,19 +2709,14 @@
     {
         std::vector<TouchedWindow> hoveringWindows =
                 getHoveringWindowsLocked(oldState, tempTouchState, entry);
+        // Hardcode to single hovering pointer for now.
+        std::bitset<MAX_POINTER_ID + 1> pointerIds;
+        pointerIds.set(entry.pointerProperties[0].id);
         for (const TouchedWindow& touchedWindow : hoveringWindows) {
-            std::optional<InputTarget> target =
-                    createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
-                                            touchedWindow.targetFlags,
-                                            touchedWindow.getDownTimeInTarget(entry.deviceId));
-            if (!target) {
-                continue;
-            }
-            // Hardcode to single hovering pointer for now.
-            std::bitset<MAX_POINTER_ID + 1> pointerIds;
-            pointerIds.set(entry.pointerProperties[0].id);
-            target->addPointers(pointerIds, touchedWindow.windowHandle->getInfo()->transform);
-            targets.push_back(*target);
+            addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+                                         touchedWindow.targetFlags, pointerIds,
+                                         touchedWindow.getDownTimeInTarget(entry.deviceId),
+                                         targets);
         }
     }
 
@@ -2630,13 +2740,13 @@
     // has a different UID, then we will not reveal coordinate information to this window.
     if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
         sp<WindowInfoHandle> foregroundWindowHandle =
-                tempTouchState.getFirstForegroundWindowHandle();
+                tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
         if (foregroundWindowHandle) {
             const auto foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
             for (InputTarget& target : targets) {
                 if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
                     sp<WindowInfoHandle> targetWindow =
-                            getWindowHandleLocked(target.inputChannel->getConnectionToken());
+                            getWindowHandleLocked(target.connection->getToken());
                     if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) {
                         target.flags |= InputTarget::Flags::ZERO_COORDS;
                     }
@@ -2655,13 +2765,13 @@
 
     // Output targets from the touch state.
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-        std::bitset<MAX_POINTER_ID + 1> touchingPointers =
+        std::vector<PointerProperties> touchingPointers =
                 touchedWindow.getTouchingPointers(entry.deviceId);
-        if (touchingPointers.none()) {
+        if (touchingPointers.empty()) {
             continue;
         }
         addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
-                                     touchedWindow.targetFlags, touchingPointers,
+                                     touchedWindow.targetFlags, getPointerIds(touchingPointers),
                                      touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
     }
 
@@ -2719,7 +2829,7 @@
     // Save changes unless the action was scroll in which case the temporary touch
     // state was only valid for this one action.
     if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
-        if (displayId >= 0) {
+        if (displayId >= ui::LogicalDisplayId::DEFAULT) {
             tempTouchState.clearWindowsWithoutPointers();
             mTouchStatesByDisplay[displayId] = tempTouchState;
         } else {
@@ -2734,7 +2844,7 @@
     return targets;
 }
 
-void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
+void InputDispatcher::finishDragAndDrop(ui::LogicalDisplayId displayId, float x, float y) {
     // Prevent stylus interceptor windows from affecting drag and drop behavior for now, until we
     // have an explicit reason to support it.
     constexpr bool isStylus = false;
@@ -2834,13 +2944,12 @@
         const sp<android::gui::WindowInfoHandle>& windowHandle,
         InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
         std::optional<nsecs_t> firstDownTimeInTarget) const {
-    std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
-    if (inputChannel == nullptr) {
+    std::shared_ptr<Connection> connection = getConnectionLocked(windowHandle->getToken());
+    if (connection == nullptr) {
         ALOGW("Not creating InputTarget for %s, no input channel", windowHandle->getName().c_str());
         return {};
     }
-    InputTarget inputTarget;
-    inputTarget.inputChannel = inputChannel;
+    InputTarget inputTarget{connection};
     inputTarget.windowHandle = windowHandle;
     inputTarget.dispatchMode = dispatchMode;
     inputTarget.flags = targetFlags;
@@ -2864,8 +2973,7 @@
     std::vector<InputTarget>::iterator it =
             std::find_if(inputTargets.begin(), inputTargets.end(),
                          [&windowHandle](const InputTarget& inputTarget) {
-                             return inputTarget.inputChannel->getConnectionToken() ==
-                                     windowHandle->getToken();
+                             return inputTarget.connection->getToken() == windowHandle->getToken();
                          });
 
     const WindowInfo* windowInfo = windowHandle->getInfo();
@@ -2905,8 +3013,7 @@
     std::vector<InputTarget>::iterator it =
             std::find_if(inputTargets.begin(), inputTargets.end(),
                          [&windowHandle](const InputTarget& inputTarget) {
-                             return inputTarget.inputChannel->getConnectionToken() ==
-                                     windowHandle->getToken();
+                             return inputTarget.connection->getToken() == windowHandle->getToken();
                          });
 
     // This is a hack, because the actual entry could potentially be an ACTION_DOWN event that
@@ -2946,17 +3053,20 @@
                    << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor;
     }
 
-    it->addPointers(pointerIds, windowInfo->transform);
+    Result<void> result = it->addPointers(pointerIds, windowInfo->transform);
+    if (!result.ok()) {
+        logDispatchStateLocked();
+        LOG(FATAL) << result.error().message();
+    }
 }
 
 void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
-                                                       int32_t displayId) {
+                                                       ui::LogicalDisplayId displayId) {
     auto monitorsIt = mGlobalMonitorsByDisplay.find(displayId);
     if (monitorsIt == mGlobalMonitorsByDisplay.end()) return;
 
     for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
-        InputTarget target;
-        target.inputChannel = monitor.inputChannel;
+        InputTarget target{monitor.connection};
         // target.firstDownTimeInTarget is not set for global monitors. It is only required in split
         // touch and global monitoring works as intended even without setting firstDownTimeInTarget
         if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
@@ -3021,9 +3131,9 @@
  * If neither of those is true, then it means the touch can be allowed.
  */
 InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLocked(
-        const sp<WindowInfoHandle>& windowHandle, int32_t x, int32_t y) const {
+        const sp<WindowInfoHandle>& windowHandle, float x, float y) const {
     const WindowInfo* windowInfo = windowHandle->getInfo();
-    int32_t displayId = windowInfo->displayId;
+    ui::LogicalDisplayId displayId = windowInfo->displayId;
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
     TouchOcclusionInfo info;
     info.hasBlockingOcclusion = false;
@@ -3035,7 +3145,8 @@
             break; // All future windows are below us. Exit early.
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
-        if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->frameContainsPoint(x, y) &&
+        if (canBeObscuredBy(windowHandle, otherHandle) &&
+            windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId)) &&
             !haveSameApplicationToken(windowInfo, otherInfo)) {
             if (DEBUG_TOUCH_OCCLUSION) {
                 info.debugInfo.push_back(
@@ -3105,8 +3216,8 @@
 }
 
 bool InputDispatcher::isWindowObscuredAtPointLocked(const sp<WindowInfoHandle>& windowHandle,
-                                                    int32_t x, int32_t y) const {
-    int32_t displayId = windowHandle->getInfo()->displayId;
+                                                    float x, float y) const {
+    ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId;
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
         if (windowHandle == otherHandle) {
@@ -3114,7 +3225,7 @@
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
         if (canBeObscuredBy(windowHandle, otherHandle) &&
-            otherInfo->frameContainsPoint(x, y)) {
+            windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId))) {
             return true;
         }
     }
@@ -3122,7 +3233,7 @@
 }
 
 bool InputDispatcher::isWindowObscuredLocked(const sp<WindowInfoHandle>& windowHandle) const {
-    int32_t displayId = windowHandle->getInfo()->displayId;
+    ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId;
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
     const WindowInfo* windowInfo = windowHandle->getInfo();
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
@@ -3130,8 +3241,7 @@
             break; // All future windows are below us. Exit early.
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
-        if (canBeObscuredBy(windowHandle, otherHandle) &&
-            otherInfo->overlaps(windowInfo)) {
+        if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->overlaps(windowInfo)) {
             return true;
         }
     }
@@ -3158,7 +3268,22 @@
         // Not poking user activity if the event type does not represent a user activity
         return;
     }
-    int32_t displayId = getTargetDisplayId(eventEntry);
+
+    const int32_t eventType = getUserActivityEventType(eventEntry);
+    if (input_flags::rate_limit_user_activity_poke_in_dispatcher()) {
+        // Note that we're directly getting the time diff between the current event and the previous
+        // event. This is assuming that the first user event always happens at a timestamp that is
+        // greater than `mMinTimeBetweenUserActivityPokes` (otherwise, the first user event will
+        // wrongly be dropped). In real life, `mMinTimeBetweenUserActivityPokes` is a much smaller
+        // value than the potential first user activity event time, so this is ok.
+        std::chrono::nanoseconds timeSinceLastEvent =
+                std::chrono::nanoseconds(eventEntry.eventTime - mLastUserActivityTimes[eventType]);
+        if (timeSinceLastEvent < mMinTimeBetweenUserActivityPokes) {
+            return;
+        }
+    }
+
+    ui::LogicalDisplayId displayId = getTargetDisplayId(eventEntry);
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
     const WindowInfo* windowDisablingUserActivityInfo = nullptr;
     if (focusedWindowHandle != nullptr) {
@@ -3168,7 +3293,6 @@
         }
     }
 
-    int32_t eventType = USER_ACTIVITY_EVENT_OTHER;
     switch (eventEntry.type) {
         case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
@@ -3182,9 +3306,6 @@
                 }
                 return;
             }
-            if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) {
-                eventType = USER_ACTIVITY_EVENT_TOUCH;
-            }
             break;
         }
         case EventEntry::Type::KEY: {
@@ -3208,7 +3329,6 @@
                 return;
             }
 
-            eventType = USER_ACTIVITY_EVENT_BUTTON;
             break;
         }
         default: {
@@ -3218,6 +3338,7 @@
         }
     }
 
+    mLastUserActivityTimes[eventType] = eventEntry.eventTime;
     auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]()
                            REQUIRES(mLock) {
                                scoped_unlock unlock(mLock);
@@ -3237,7 +3358,7 @@
         ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, "
               "globalScaleFactor=%f, pointerIds=%s %s",
               connection->getInputChannelName().c_str(), inputTarget.flags.string().c_str(),
-              inputTarget.globalScaleFactor, bitsetToString(inputTarget.pointerIds).c_str(),
+              inputTarget.globalScaleFactor, bitsetToString(inputTarget.getPointerIds()).c_str(),
               inputTarget.getPointerInfoString().c_str());
     }
 
@@ -3259,7 +3380,7 @@
                             ftl::enum_string(eventEntry->type).c_str());
 
         const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
-        if (inputTarget.pointerIds.count() != originalMotionEntry.getPointerCount()) {
+        if (inputTarget.getPointerIds().count() != originalMotionEntry.getPointerCount()) {
             if (!inputTarget.firstDownTimeInTarget.has_value()) {
                 logDispatchStateLocked();
                 LOG(FATAL) << "Splitting motion events requires a down time to be set for the "
@@ -3268,7 +3389,7 @@
                            << originalMotionEntry.getDescription();
             }
             std::unique_ptr<MotionEntry> splitMotionEntry =
-                    splitMotionEvent(originalMotionEntry, inputTarget.pointerIds,
+                    splitMotionEvent(originalMotionEntry, inputTarget.getPointerIds(),
                                      inputTarget.firstDownTimeInTarget.value());
             if (!splitMotionEntry) {
                 return; // split event was dropped
@@ -3316,10 +3437,18 @@
 void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
                                                  std::shared_ptr<const EventEntry> eventEntry,
                                                  const InputTarget& inputTarget) {
+    const bool isKeyOrMotion = eventEntry->type == EventEntry::Type::KEY ||
+            eventEntry->type == EventEntry::Type::MOTION;
+    if (isKeyOrMotion && !inputTarget.windowHandle && !connection->monitor) {
+        LOG(FATAL) << "All InputTargets for non-monitors must be associated with a window; target: "
+                   << inputTarget << " connection: " << connection->getInputChannelName()
+                   << " entry: " << eventEntry->getDescription();
+    }
     // This is a new event.
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
     std::unique_ptr<DispatchEntry> dispatchEntry =
-            createDispatchEntry(inputTarget, eventEntry, inputTarget.flags);
+            createDispatchEntry(mIdGenerator, inputTarget, eventEntry, inputTarget.flags,
+                                mWindowInfosVsyncId, mTracer.get());
 
     // Use the eventEntry from dispatchEntry since the entry may have changed and can now be a
     // different EventEntry than what was passed in.
@@ -3377,30 +3506,56 @@
                             InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED)) {
                     resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
                 }
+                if (dispatchEntry->targetFlags.test(InputTarget::Flags::NO_FOCUS_CHANGE)) {
+                    resolvedFlags |= AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+                }
 
                 dispatchEntry->resolvedFlags = resolvedFlags;
                 if (resolvedAction != motionEntry.action) {
-                    // Generate a new MotionEntry with a new eventId using the resolved action and
-                    // flags.
-                    resolvedMotion =
-                            std::make_shared<MotionEntry>(mIdGenerator.nextId(),
-                                                          motionEntry.injectionState,
-                                                          motionEntry.eventTime,
-                                                          motionEntry.deviceId, motionEntry.source,
-                                                          motionEntry.displayId,
-                                                          motionEntry.policyFlags, resolvedAction,
-                                                          motionEntry.actionButton, resolvedFlags,
-                                                          motionEntry.metaState,
-                                                          motionEntry.buttonState,
-                                                          motionEntry.classification,
-                                                          motionEntry.edgeFlags,
-                                                          motionEntry.xPrecision,
-                                                          motionEntry.yPrecision,
-                                                          motionEntry.xCursorPosition,
-                                                          motionEntry.yCursorPosition,
-                                                          motionEntry.downTime,
-                                                          motionEntry.pointerProperties,
-                                                          motionEntry.pointerCoords);
+                    std::optional<std::vector<PointerProperties>> usingProperties;
+                    std::optional<std::vector<PointerCoords>> usingCoords;
+                    if (resolvedAction == AMOTION_EVENT_ACTION_HOVER_EXIT ||
+                        resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
+                        // This is a HOVER_EXIT or an ACTION_CANCEL event that was synthesized by
+                        // the dispatcher, and therefore the coordinates of this event are currently
+                        // incorrect. These events should use the coordinates of the last dispatched
+                        // ACTION_MOVE or HOVER_MOVE. We need to query InputState to get this data.
+                        const bool hovering = resolvedAction == AMOTION_EVENT_ACTION_HOVER_EXIT;
+                        std::optional<std::pair<std::vector<PointerProperties>,
+                                                std::vector<PointerCoords>>>
+                                pointerInfo =
+                                        connection->inputState.getPointersOfLastEvent(motionEntry,
+                                                                                      hovering);
+                        if (pointerInfo) {
+                            usingProperties = pointerInfo->first;
+                            usingCoords = pointerInfo->second;
+                        }
+                    }
+                    {
+                        // Generate a new MotionEntry with a new eventId using the resolved action
+                        // and flags, and set it as the resolved entry.
+                        auto newEntry = std::make_shared<
+                                MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState,
+                                             motionEntry.eventTime, motionEntry.deviceId,
+                                             motionEntry.source, motionEntry.displayId,
+                                             motionEntry.policyFlags, resolvedAction,
+                                             motionEntry.actionButton, resolvedFlags,
+                                             motionEntry.metaState, motionEntry.buttonState,
+                                             motionEntry.classification, motionEntry.edgeFlags,
+                                             motionEntry.xPrecision, motionEntry.yPrecision,
+                                             motionEntry.xCursorPosition,
+                                             motionEntry.yCursorPosition, motionEntry.downTime,
+                                             usingProperties.value_or(
+                                                     motionEntry.pointerProperties),
+                                             usingCoords.value_or(motionEntry.pointerCoords));
+                        if (mTracer) {
+                            ensureEventTraced(motionEntry);
+                            newEntry->traceTracker =
+                                    mTracer->traceDerivedEvent(*newEntry,
+                                                               *motionEntry.traceTracker);
+                        }
+                        resolvedMotion = newEntry;
+                    }
                     if (ATRACE_ENABLED()) {
                         std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32
                                                            ") to MotionEvent(id=0x%" PRIx32 ").",
@@ -3423,9 +3578,14 @@
                 LOG(INFO) << "Canceling pointers for device " << resolvedMotion->deviceId << " in "
                           << connection->getInputChannelName() << " with event "
                           << cancelEvent->getDescription();
+                if (mTracer) {
+                    static_cast<MotionEntry&>(*cancelEvent).traceTracker =
+                            mTracer->traceDerivedEvent(*cancelEvent, *resolvedMotion->traceTracker);
+                }
                 std::unique_ptr<DispatchEntry> cancelDispatchEntry =
-                        createDispatchEntry(inputTarget, std::move(cancelEvent),
-                                            ftl::Flags<InputTarget::Flags>());
+                        createDispatchEntry(mIdGenerator, inputTarget, std::move(cancelEvent),
+                                            ftl::Flags<InputTarget::Flags>(), mWindowInfosVsyncId,
+                                            mTracer.get());
 
                 // Send these cancel events to the queue before sending the event from the new
                 // device.
@@ -3438,15 +3598,14 @@
                              << "~ dropping inconsistent event: " << *dispatchEntry;
                 return; // skip the inconsistent event
             }
-
-            if ((resolvedMotion->flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
+            if ((dispatchEntry->resolvedFlags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
                 (resolvedMotion->policyFlags & POLICY_FLAG_TRUSTED)) {
                 // Skip reporting pointer down outside focus to the policy.
                 break;
             }
 
             dispatchPointerDownOutsideFocus(resolvedMotion->source, resolvedMotion->action,
-                                            inputTarget.inputChannel->getConnectionToken());
+                                            inputTarget.connection->getToken());
 
             break;
         }
@@ -3529,13 +3688,9 @@
             continue; // Skip windows that receive ACTION_OUTSIDE
         }
 
-        sp<IBinder> token = target.inputChannel->getConnectionToken();
-        std::shared_ptr<Connection> connection = getConnectionLocked(token);
-        if (connection == nullptr) {
-            continue;
-        }
+        sp<IBinder> token = target.connection->getToken();
         newConnectionTokens.insert(std::move(token));
-        newConnections.emplace_back(connection);
+        newConnections.emplace_back(target.connection);
         if (target.windowHandle) {
             interactionUids.emplace(target.windowHandle->getInfo()->ownerUid);
         }
@@ -3555,7 +3710,7 @@
 
     std::string targetList;
     for (const std::shared_ptr<Connection>& connection : newConnections) {
-        targetList += connection->getWindowName() + ", ";
+        targetList += connection->getInputChannelName() + ", ";
     }
     std::string message = "Interaction with: " + targetList;
     if (targetList.empty()) {
@@ -3593,6 +3748,7 @@
     PointerCoords scaledCoords[MAX_POINTERS];
     const PointerCoords* usingCoords = motionEntry.pointerCoords.data();
 
+    // TODO(b/316355518): Do not modify coords before dispatch.
     // Set the X and Y offset and X and Y scale depending on the input source.
     if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
         !(dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS))) {
@@ -3607,12 +3763,6 @@
             }
             usingCoords = scaledCoords;
         }
-    } else if (dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS)) {
-        // We don't want the dispatch target to know the coordinates
-        for (uint32_t i = 0; i < motionEntry.getPointerCount(); i++) {
-            scaledCoords[i].clear();
-        }
-        usingCoords = scaledCoords;
     }
 
     std::array<uint8_t, 32> hmac = getSignature(motionEntry, dispatchEntry);
@@ -3668,6 +3818,10 @@
                                                   keyEntry.keyCode, keyEntry.scanCode,
                                                   keyEntry.metaState, keyEntry.repeatCount,
                                                   keyEntry.downTime, keyEntry.eventTime);
+                if (mTracer) {
+                    ensureEventTraced(keyEntry);
+                    mTracer->traceEventDispatch(*dispatchEntry, *keyEntry.traceTracker);
+                }
                 break;
             }
 
@@ -3676,7 +3830,12 @@
                     LOG(INFO) << "Publishing " << *dispatchEntry << " to "
                               << connection->getInputChannelName();
                 }
+                const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
                 status = publishMotionEvent(*connection, *dispatchEntry);
+                if (mTracer) {
+                    ensureEventTraced(motionEntry);
+                    mTracer->traceEventDispatch(*dispatchEntry, *motionEntry.traceTracker);
+                }
                 break;
             }
 
@@ -3701,9 +3860,10 @@
             case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
                 const auto& captureEntry =
                         static_cast<const PointerCaptureChangedEntry&>(eventEntry);
-                status = connection->inputPublisher
-                                 .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
-                                                      captureEntry.pointerCaptureRequest.enable);
+                status =
+                        connection->inputPublisher
+                                .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
+                                                     captureEntry.pointerCaptureRequest.isEnable());
                 break;
             }
 
@@ -3761,7 +3921,7 @@
         connection->outboundQueue.erase(connection->outboundQueue.begin());
         traceOutboundQueueLength(*connection);
         if (connection->responsive) {
-            mAnrTracker.insert(timeoutTime, connection->inputChannel->getConnectionToken());
+            mAnrTracker.insert(timeoutTime, connection->getToken());
         }
         traceWaitQueueLength(*connection);
     }
@@ -3814,8 +3974,7 @@
               connection->getInputChannelName().c_str(), seq, toString(handled));
     }
 
-    if (connection->status == Connection::Status::BROKEN ||
-        connection->status == Connection::Status::ZOMBIE) {
+    if (connection->status != Connection::Status::NORMAL) {
         return;
     }
 
@@ -3852,7 +4011,7 @@
 
             auto command = [this, connection]() REQUIRES(mLock) {
                 scoped_unlock unlock(mLock);
-                mPolicy.notifyInputChannelBroken(connection->inputChannel->getConnectionToken());
+                mPolicy.notifyInputChannelBroken(connection->getToken());
             };
             postCommandLocked(std::move(command));
         }
@@ -3910,10 +4069,9 @@
                 if (shouldReportMetricsForConnection(*connection)) {
                     const InputPublisher::Timeline& timeline =
                             std::get<InputPublisher::Timeline>(*result);
-                    mLatencyTracker
-                            .trackGraphicsLatency(timeline.inputEventId,
-                                                  connection->inputChannel->getConnectionToken(),
-                                                  std::move(timeline.graphicsTimeline));
+                    mLatencyTracker.trackGraphicsLatency(timeline.inputEventId,
+                                                         connection->getToken(),
+                                                         std::move(timeline.graphicsTimeline));
                 }
             }
             gotOne = true;
@@ -3934,8 +4092,7 @@
     } else {
         // Monitor channels are never explicitly unregistered.
         // We do it automatically when the remote endpoint is closed so don't warn about them.
-        const bool stillHaveWindowHandle =
-                getWindowHandleLocked(connection->inputChannel->getConnectionToken()) != nullptr;
+        const bool stillHaveWindowHandle = getWindowHandleLocked(connection->getToken()) != nullptr;
         notify = !connection->monitor && stillHaveWindowHandle;
         if (notify) {
             ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred.  events=0x%x",
@@ -3944,39 +4101,85 @@
     }
 
     // Remove the channel.
-    removeInputChannelLocked(connection->inputChannel->getConnectionToken(), notify);
+    removeInputChannelLocked(connection->getToken(), notify);
     return 0; // remove the callback
 }
 
 void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked(
         const CancelationOptions& options) {
-    for (const auto& [token, connection] : mConnectionsByToken) {
-        synthesizeCancelationEventsForConnectionLocked(connection, options);
+    // Cancel windows (i.e. non-monitors).
+    // A channel must have at least one window to receive any input. If a window was removed, the
+    // event streams directed to the window will already have been canceled during window removal.
+    // So there is no need to generate cancellations for connections without any windows.
+    const auto [cancelPointers, cancelNonPointers] = expandCancellationMode(options.mode);
+    // Generate cancellations for touched windows first. This is to avoid generating cancellations
+    // through a non-touched window if there are more than one window for an input channel.
+    if (cancelPointers) {
+        for (const auto& [displayId, touchState] : mTouchStatesByDisplay) {
+            if (options.displayId.has_value() && options.displayId != displayId) {
+                continue;
+            }
+            for (const auto& touchedWindow : touchState.windows) {
+                synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
+            }
+        }
     }
+    // Follow up by generating cancellations for all windows, because we don't explicitly track
+    // the windows that have an ongoing focus event stream.
+    if (cancelNonPointers) {
+        for (const auto& [_, handles] : mWindowHandlesByDisplay) {
+            for (const auto& windowHandle : handles) {
+                synthesizeCancelationEventsForWindowLocked(windowHandle, options);
+            }
+        }
+    }
+
+    // Cancel monitors.
+    synthesizeCancelationEventsForMonitorsLocked(options);
 }
 
 void InputDispatcher::synthesizeCancelationEventsForMonitorsLocked(
         const CancelationOptions& options) {
     for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
         for (const Monitor& monitor : monitors) {
-            synthesizeCancelationEventsForInputChannelLocked(monitor.inputChannel, options);
+            synthesizeCancelationEventsForConnectionLocked(monitor.connection, options,
+                                                           /*window=*/nullptr);
         }
     }
 }
 
-void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
-        const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options) {
-    std::shared_ptr<Connection> connection = getConnectionLocked(channel->getConnectionToken());
-    if (connection == nullptr) {
-        return;
+void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
+        const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options,
+        const std::shared_ptr<Connection>& connection) {
+    if (windowHandle == nullptr) {
+        LOG(FATAL) << __func__ << ": Window handle must not be null";
+    }
+    if (connection) {
+        // The connection can be optionally provided to avoid multiple lookups.
+        if (windowHandle->getToken() != connection->getToken()) {
+            LOG(FATAL) << __func__
+                       << ": Wrong connection provided for window: " << windowHandle->getName();
+        }
     }
 
-    synthesizeCancelationEventsForConnectionLocked(connection, options);
+    std::shared_ptr<Connection> resolvedConnection =
+            connection ? connection : getConnectionLocked(windowHandle->getToken());
+    if (!resolvedConnection) {
+        LOG(DEBUG) << __func__ << "No connection found for window: " << windowHandle->getName();
+        return;
+    }
+    synthesizeCancelationEventsForConnectionLocked(resolvedConnection, options, windowHandle);
 }
 
 void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
-        const std::shared_ptr<Connection>& connection, const CancelationOptions& options) {
-    if (connection->status == Connection::Status::BROKEN) {
+        const std::shared_ptr<Connection>& connection, const CancelationOptions& options,
+        const sp<WindowInfoHandle>& window) {
+    if (!connection->monitor && window == nullptr) {
+        LOG(FATAL) << __func__
+                   << ": Cannot send event to non-monitor channel without a window - channel: "
+                   << connection->getInputChannelName();
+    }
+    if (connection->status != Connection::Status::NORMAL) {
         return;
     }
 
@@ -4002,8 +4205,8 @@
 
     const bool wasEmpty = connection->outboundQueue.empty();
     // The target to use if we don't find a window associated with the channel.
-    const InputTarget fallbackTarget{.inputChannel = connection->inputChannel};
-    const auto& token = connection->inputChannel->getConnectionToken();
+    const InputTarget fallbackTarget{connection};
+    const auto& token = connection->getToken();
 
     for (size_t i = 0; i < cancelationEvents.size(); i++) {
         std::unique_ptr<EventEntry> cancelationEventEntry = std::move(cancelationEvents[i]);
@@ -4011,11 +4214,13 @@
 
         switch (cancelationEventEntry->type) {
             case EventEntry::Type::KEY: {
+                if (mTracer) {
+                    static_cast<KeyEntry&>(*cancelationEventEntry).traceTracker =
+                            mTracer->traceDerivedEvent(*cancelationEventEntry,
+                                                       *options.traceTracker);
+                }
                 const auto& keyEntry = static_cast<const KeyEntry&>(*cancelationEventEntry);
-                const std::optional<int32_t> targetDisplay = keyEntry.displayId != ADISPLAY_ID_NONE
-                        ? std::make_optional(keyEntry.displayId)
-                        : std::nullopt;
-                if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
+                if (window) {
                     addWindowTargetLocked(window, InputTarget::DispatchMode::AS_IS,
                                           /*targetFlags=*/{}, keyEntry.downTime, targets);
                 } else {
@@ -4025,12 +4230,13 @@
                 break;
             }
             case EventEntry::Type::MOTION: {
+                if (mTracer) {
+                    static_cast<MotionEntry&>(*cancelationEventEntry).traceTracker =
+                            mTracer->traceDerivedEvent(*cancelationEventEntry,
+                                                       *options.traceTracker);
+                }
                 const auto& motionEntry = static_cast<const MotionEntry&>(*cancelationEventEntry);
-                const std::optional<int32_t> targetDisplay =
-                        motionEntry.displayId != ADISPLAY_ID_NONE
-                        ? std::make_optional(motionEntry.displayId)
-                        : std::nullopt;
-                if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
+                if (window) {
                     std::bitset<MAX_POINTER_ID + 1> pointerIds;
                     for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount();
                          pointerIndex++) {
@@ -4076,6 +4282,9 @@
         }
 
         if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+        if (mTracer) {
+            mTracer->dispatchToTargetHint(*options.traceTracker, targets[0]);
+        }
         enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), targets[0]);
     }
 
@@ -4087,8 +4296,9 @@
 
 void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
         const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
-        ftl::Flags<InputTarget::Flags> targetFlags) {
-    if (connection->status == Connection::Status::BROKEN) {
+        ftl::Flags<InputTarget::Flags> targetFlags,
+        const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) {
+    if (connection->status != Connection::Status::NORMAL) {
         return;
     }
 
@@ -4104,14 +4314,22 @@
               connection->getInputChannelName().c_str(), downEvents.size());
     }
 
-    sp<WindowInfoHandle> windowHandle =
-            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
+    const auto [_, touchedWindowState, displayId] =
+            findTouchStateWindowAndDisplayLocked(connection->getToken());
+    if (touchedWindowState == nullptr) {
+        LOG(FATAL) << __func__ << ": Touch state is out of sync: No touched window for token";
+    }
+    const auto& windowHandle = touchedWindowState->windowHandle;
 
     const bool wasEmpty = connection->outboundQueue.empty();
     for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) {
         std::vector<InputTarget> targets{};
         switch (downEventEntry->type) {
             case EventEntry::Type::MOTION: {
+                if (mTracer) {
+                    static_cast<MotionEntry&>(*downEventEntry).traceTracker =
+                            mTracer->traceDerivedEvent(*downEventEntry, *traceTracker);
+                }
                 const auto& motionEntry = static_cast<const MotionEntry&>(*downEventEntry);
                 if (windowHandle != nullptr) {
                     std::bitset<MAX_POINTER_ID + 1> pointerIds;
@@ -4123,8 +4341,7 @@
                                                  targetFlags, pointerIds, motionEntry.downTime,
                                                  targets);
                 } else {
-                    targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel,
-                                                     .flags = targetFlags});
+                    targets.emplace_back(connection, targetFlags);
                     const auto it = mDisplayInfos.find(motionEntry.displayId);
                     if (it != mDisplayInfos.end()) {
                         targets.back().displayTransform = it->second.transform;
@@ -4150,6 +4367,9 @@
         }
 
         if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+        if (mTracer) {
+            mTracer->dispatchToTargetHint(*traceTracker, targets[0]);
+        }
         enqueueDispatchEntryLocked(connection, std::move(downEventEntry), targets[0]);
     }
 
@@ -4159,86 +4379,30 @@
     }
 }
 
-void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
-        const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options) {
-    if (windowHandle != nullptr) {
-        std::shared_ptr<Connection> wallpaperConnection =
-                getConnectionLocked(windowHandle->getToken());
-        if (wallpaperConnection != nullptr) {
-            synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options);
-        }
-    }
-}
-
 std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
         const MotionEntry& originalMotionEntry, std::bitset<MAX_POINTER_ID + 1> pointerIds,
         nsecs_t splitDownTime) {
-    ALOG_ASSERT(pointerIds.any());
-
-    uint32_t splitPointerIndexMap[MAX_POINTERS];
-    std::vector<PointerProperties> splitPointerProperties;
-    std::vector<PointerCoords> splitPointerCoords;
-
-    uint32_t originalPointerCount = originalMotionEntry.getPointerCount();
-    uint32_t splitPointerCount = 0;
-
-    for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
-         originalPointerIndex++) {
-        const PointerProperties& pointerProperties =
-                originalMotionEntry.pointerProperties[originalPointerIndex];
-        uint32_t pointerId = uint32_t(pointerProperties.id);
-        if (pointerIds.test(pointerId)) {
-            splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
-            splitPointerProperties.push_back(pointerProperties);
-            splitPointerCoords.push_back(originalMotionEntry.pointerCoords[originalPointerIndex]);
-            splitPointerCount += 1;
-        }
-    }
-
-    if (splitPointerCount != pointerIds.count()) {
+    const auto& [action, pointerProperties, pointerCoords] =
+            MotionEvent::split(originalMotionEntry.action, originalMotionEntry.flags,
+                               /*historySize=*/0, originalMotionEntry.pointerProperties,
+                               originalMotionEntry.pointerCoords, pointerIds);
+    if (pointerIds.count() != pointerCoords.size()) {
+        // TODO(b/329107108): Determine why some IDs in pointerIds were not in originalMotionEntry.
         // This is bad.  We are missing some of the pointers that we expected to deliver.
         // Most likely this indicates that we received an ACTION_MOVE events that has
         // different pointer ids than we expected based on the previous ACTION_DOWN
         // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
         // in this way.
-        ALOGW("Dropping split motion event because the pointer count is %d but "
+        ALOGW("Dropping split motion event because the pointer count is %zu but "
               "we expected there to be %zu pointers.  This probably means we received "
               "a broken sequence of pointer ids from the input device: %s",
-              splitPointerCount, pointerIds.count(), originalMotionEntry.getDescription().c_str());
+              pointerCoords.size(), pointerIds.count(),
+              originalMotionEntry.getDescription().c_str());
         return nullptr;
     }
 
-    int32_t action = originalMotionEntry.action;
-    int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
-    if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
-        maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
-        int32_t originalPointerIndex = MotionEvent::getActionIndex(action);
-        const PointerProperties& pointerProperties =
-                originalMotionEntry.pointerProperties[originalPointerIndex];
-        uint32_t pointerId = uint32_t(pointerProperties.id);
-        if (pointerIds.test(pointerId)) {
-            if (pointerIds.count() == 1) {
-                // The first/last pointer went down/up.
-                action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
-                        ? AMOTION_EVENT_ACTION_DOWN
-                        : (originalMotionEntry.flags & AMOTION_EVENT_FLAG_CANCELED) != 0
-                                ? AMOTION_EVENT_ACTION_CANCEL
-                                : AMOTION_EVENT_ACTION_UP;
-            } else {
-                // A secondary pointer went down/up.
-                uint32_t splitPointerIndex = 0;
-                while (pointerId != uint32_t(splitPointerProperties[splitPointerIndex].id)) {
-                    splitPointerIndex += 1;
-                }
-                action = maskedAction |
-                        (splitPointerIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-            }
-        } else {
-            // An unrelated pointer changed.
-            action = AMOTION_EVENT_ACTION_MOVE;
-        }
-    }
-
+    // TODO(b/327503168): Move this check inside MotionEvent::split once all callers handle it
+    //   correctly.
     if (action == AMOTION_EVENT_ACTION_DOWN && splitDownTime != originalMotionEntry.eventTime) {
         logDispatchStateLocked();
         LOG_ALWAYS_FATAL("Split motion event has mismatching downTime and eventTime for "
@@ -4266,7 +4430,11 @@
                                           originalMotionEntry.yPrecision,
                                           originalMotionEntry.xCursorPosition,
                                           originalMotionEntry.yCursorPosition, splitDownTime,
-                                          splitPointerProperties, splitPointerCoords);
+                                          pointerProperties, pointerCoords);
+    if (mTracer) {
+        splitMotionEntry->traceTracker =
+                mTracer->traceDerivedEvent(*splitMotionEntry, *originalMotionEntry.traceTracker);
+    }
 
     return splitMotionEntry;
 }
@@ -4298,12 +4466,13 @@
 void InputDispatcher::notifyKey(const NotifyKeyArgs& args) {
     ALOGD_IF(debugInboundEventDetails(),
              "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64
-             ", deviceId=%d, source=%s, displayId=%" PRId32
-             "policyFlags=0x%x, action=%s, flags=0x%x, keyCode=%s, scanCode=0x%x, metaState=0x%x, "
+             ", deviceId=%d, source=%s, displayId=%s, policyFlags=0x%x, action=%s, flags=0x%x, "
+             "keyCode=%s, scanCode=0x%x, metaState=0x%x, "
              "downTime=%" PRId64,
              args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
-             args.displayId, args.policyFlags, KeyEvent::actionToString(args.action), args.flags,
-             KeyEvent::getLabel(args.keyCode), args.scanCode, args.metaState, args.downTime);
+             args.displayId.toString().c_str(), args.policyFlags,
+             KeyEvent::actionToString(args.action), args.flags, KeyEvent::getLabel(args.keyCode),
+             args.scanCode, args.metaState, args.downTime);
     Result<void> keyCheck = validateKeyEvent(args.action);
     if (!keyCheck.ok()) {
         LOG(ERROR) << "invalid key event: " << keyCheck.error();
@@ -4359,6 +4528,9 @@
                                            args.deviceId, args.source, args.displayId, policyFlags,
                                            args.action, flags, keyCode, args.scanCode, metaState,
                                            repeatCount, args.downTime);
+        if (mTracer) {
+            newEntry->traceTracker = mTracer->traceInboundEvent(*newEntry);
+        }
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
         mLock.unlock();
@@ -4376,15 +4548,15 @@
 void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) {
     if (debugInboundEventDetails()) {
         ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, "
-              "displayId=%" PRId32 ", policyFlags=0x%x, "
+              "displayId=%s, policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, "
               "yCursorPosition=%f, downTime=%" PRId64,
               args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
-              args.displayId, args.policyFlags, MotionEvent::actionToString(args.action).c_str(),
-              args.actionButton, args.flags, args.metaState, args.buttonState, args.edgeFlags,
-              args.xPrecision, args.yPrecision, args.xCursorPosition, args.yCursorPosition,
-              args.downTime);
+              args.displayId.toString().c_str(), args.policyFlags,
+              MotionEvent::actionToString(args.action).c_str(), args.actionButton, args.flags,
+              args.metaState, args.buttonState, args.edgeFlags, args.xPrecision, args.yPrecision,
+              args.xCursorPosition, args.yCursorPosition, args.downTime);
         for (uint32_t i = 0; i < args.getPointerCount(); i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, "
                   "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",
@@ -4413,7 +4585,8 @@
     if (DEBUG_VERIFY_EVENTS) {
         auto [it, _] =
                 mVerifiersByDisplay.try_emplace(args.displayId,
-                                                StringPrintf("display %" PRId32, args.displayId));
+                                                StringPrintf("display %s",
+                                                             args.displayId.toString().c_str()));
         Result<void> result =
                 it->second.processMovement(args.deviceId, args.source, args.action,
                                            args.getPointerCount(), args.pointerProperties.data(),
@@ -4427,7 +4600,8 @@
     policyFlags |= POLICY_FLAG_TRUSTED;
 
     android::base::Timer t;
-    mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags);
+    mPolicy.interceptMotionBeforeQueueing(args.displayId, args.source, args.action, args.eventTime,
+                                          policyFlags);
     if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
         ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
               std::to_string(t.duration().count()).c_str());
@@ -4484,6 +4658,9 @@
                                               args.yPrecision, args.xCursorPosition,
                                               args.yCursorPosition, args.downTime,
                                               args.pointerProperties, args.pointerCoords);
+        if (mTracer) {
+            newEntry->traceTracker = mTracer->traceInboundEvent(*newEntry);
+        }
 
         if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
             IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&
@@ -4555,6 +4732,7 @@
 }
 
 void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    // TODO(b/308677868) Remove device reset from the InputListener interface
     if (debugInboundEventDetails()) {
         ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime,
               args.deviceId);
@@ -4581,7 +4759,7 @@
 void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
     if (debugInboundEventDetails()) {
         ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args.eventTime,
-              args.request.enable ? "true" : "false");
+              args.request.isEnable() ? "true" : "false");
     }
 
     bool needWake = false;
@@ -4671,6 +4849,9 @@
                                                incomingKey.getScanCode(), metaState,
                                                incomingKey.getRepeatCount(),
                                                incomingKey.getDownTime());
+            if (mTracer) {
+                injectedEntry->traceTracker = mTracer->traceInboundEvent(*injectedEntry);
+            }
             injectedEntries.push(std::move(injectedEntry));
             break;
         }
@@ -4680,15 +4861,18 @@
             const bool isPointerEvent =
                     isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER);
             // If a pointer event has no displayId specified, inject it to the default display.
-            const int32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE)
-                    ? ADISPLAY_ID_DEFAULT
+            const ui::LogicalDisplayId displayId =
+                    isPointerEvent && (event->getDisplayId() == ui::LogicalDisplayId::INVALID)
+                    ? ui::LogicalDisplayId::DEFAULT
                     : event->getDisplayId();
             int32_t flags = motionEvent.getFlags();
 
             if (!(policyFlags & POLICY_FLAG_FILTERED)) {
                 nsecs_t eventTime = motionEvent.getEventTime();
                 android::base::Timer t;
-                mPolicy.interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags);
+                mPolicy.interceptMotionBeforeQueueing(displayId, motionEvent.getSource(),
+                                                      motionEvent.getAction(), eventTime,
+                                                      /*byref*/ policyFlags);
                 if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
                     ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
                           std::to_string(t.duration().count()).c_str());
@@ -4700,6 +4884,30 @@
             }
 
             mLock.lock();
+
+            if (policyFlags & POLICY_FLAG_FILTERED) {
+                // The events from InputFilter impersonate real hardware devices. Check these
+                // events for consistency and print an error. An inconsistent event sent from
+                // InputFilter could cause a crash in the later stages of dispatching pipeline.
+                auto [it, _] =
+                        mInputFilterVerifiersByDisplay.try_emplace(displayId,
+                                                                   std::string("Injection on ") +
+                                                                           displayId.toString());
+                InputVerifier& verifier = it->second;
+
+                Result<void> result =
+                        verifier.processMovement(resolvedDeviceId, motionEvent.getSource(),
+                                                 motionEvent.getAction(),
+                                                 motionEvent.getPointerCount(),
+                                                 motionEvent.getPointerProperties(),
+                                                 motionEvent.getSamplePointerCoords(), flags);
+                if (!result.ok()) {
+                    logDispatchStateLocked();
+                    LOG(ERROR) << "Inconsistent event: " << motionEvent
+                               << ", reason: " << result.error();
+                }
+            }
+
             const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes();
             const size_t pointerCount = motionEvent.getPointerCount();
             const std::vector<PointerProperties>
@@ -4726,6 +4934,9 @@
                                                                              samplePointerCoords +
                                                                                      pointerCount));
             transformMotionEntryForInjectionLocked(*injectedEntry, motionEvent.getTransform());
+            if (mTracer) {
+                injectedEntry->traceTracker = mTracer->traceInboundEvent(*injectedEntry);
+            }
             injectedEntries.push(std::move(injectedEntry));
             for (size_t i = motionEvent.getHistorySize(); i > 0; i--) {
                 sampleEventTimes += 1;
@@ -4746,6 +4957,10 @@
                                                                         pointerCount));
                 transformMotionEntryForInjectionLocked(*nextInjectedEntry,
                                                        motionEvent.getTransform());
+                if (mTracer) {
+                    nextInjectedEntry->traceTracker =
+                            mTracer->traceInboundEvent(*nextInjectedEntry);
+                }
                 injectedEntries.push(std::move(nextInjectedEntry));
             }
             break;
@@ -4848,7 +5063,7 @@
             break;
         }
         default: {
-            ALOGE("Cannot verify events of type %" PRId32, event.getType());
+            LOG(ERROR) << "Cannot verify events of type " << ftl::enum_string(event.getType());
             return nullptr;
         }
     }
@@ -4939,22 +5154,21 @@
 }
 
 const std::vector<sp<WindowInfoHandle>>& InputDispatcher::getWindowHandlesLocked(
-        int32_t displayId) const {
+        ui::LogicalDisplayId displayId) const {
     static const std::vector<sp<WindowInfoHandle>> EMPTY_WINDOW_HANDLES;
     auto it = mWindowHandlesByDisplay.find(displayId);
     return it != mWindowHandlesByDisplay.end() ? it->second : EMPTY_WINDOW_HANDLES;
 }
 
 sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
-        const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId) const {
+        const sp<IBinder>& windowHandleToken, std::optional<ui::LogicalDisplayId> displayId) const {
     if (windowHandleToken == nullptr) {
         return nullptr;
     }
 
     if (!displayId) {
         // Look through all displays.
-        for (auto& it : mWindowHandlesByDisplay) {
-            const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
+        for (const auto& [_, windowHandles] : mWindowHandlesByDisplay) {
             for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
                 if (windowHandle->getToken() == windowHandleToken) {
                     return windowHandle;
@@ -4975,16 +5189,15 @@
 
 sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
         const sp<WindowInfoHandle>& windowHandle) const {
-    for (auto& it : mWindowHandlesByDisplay) {
-        const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
+    for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
         for (const sp<WindowInfoHandle>& handle : windowHandles) {
             if (handle->getId() == windowHandle->getId() &&
                 handle->getToken() == windowHandle->getToken()) {
-                if (windowHandle->getInfo()->displayId != it.first) {
-                    ALOGE("Found window %s in display %" PRId32
-                          ", but it should belong to display %" PRId32,
-                          windowHandle->getName().c_str(), it.first,
-                          windowHandle->getInfo()->displayId);
+                if (windowHandle->getInfo()->displayId != displayId) {
+                    ALOGE("Found window %s in display %s"
+                          ", but it should belong to display %s",
+                          windowHandle->getName().c_str(), displayId.toString().c_str(),
+                          windowHandle->getInfo()->displayId.toString().c_str());
                 }
                 return handle;
             }
@@ -4993,12 +5206,13 @@
     return nullptr;
 }
 
-sp<WindowInfoHandle> InputDispatcher::getFocusedWindowHandleLocked(int displayId) const {
+sp<WindowInfoHandle> InputDispatcher::getFocusedWindowHandleLocked(
+        ui::LogicalDisplayId displayId) const {
     sp<IBinder> focusedToken = mFocusResolver.getFocusedWindowToken(displayId);
     return getWindowHandleLocked(focusedToken, displayId);
 }
 
-ui::Transform InputDispatcher::getTransformLocked(int32_t displayId) const {
+ui::Transform InputDispatcher::getTransformLocked(ui::LogicalDisplayId displayId) const {
     auto displayInfoIt = mDisplayInfos.find(displayId);
     return displayInfoIt != mDisplayInfos.end() ? displayInfoIt->second.transform
                                                 : kIdentityTransform;
@@ -5056,20 +5270,19 @@
         return false;
     }
 
+    // Ignore touches if stylus is down anywhere on screen
+    if (info.inputConfig.test(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH) &&
+        isStylusActiveInDisplay(info.displayId, mTouchStatesByDisplay)) {
+        LOG(INFO) << "Dropping touch from " << window->getName() << " because stylus is active";
+        return false;
+    }
+
     return true;
 }
 
-std::shared_ptr<InputChannel> InputDispatcher::getInputChannelLocked(
-        const sp<IBinder>& token) const {
-    auto connectionIt = mConnectionsByToken.find(token);
-    if (connectionIt == mConnectionsByToken.end()) {
-        return nullptr;
-    }
-    return connectionIt->second->inputChannel;
-}
-
 void InputDispatcher::updateWindowHandlesForDisplayLocked(
-        const std::vector<sp<WindowInfoHandle>>& windowInfoHandles, int32_t displayId) {
+        const std::vector<sp<WindowInfoHandle>>& windowInfoHandles,
+        ui::LogicalDisplayId displayId) {
     if (windowInfoHandles.empty()) {
         // Remove all handles on a display if there are no windows left.
         mWindowHandlesByDisplay.erase(displayId);
@@ -5087,7 +5300,7 @@
     std::vector<sp<WindowInfoHandle>> newHandles;
     for (const sp<WindowInfoHandle>& handle : windowInfoHandles) {
         const WindowInfo* info = handle->getInfo();
-        if (getInputChannelLocked(handle->getToken()) == nullptr) {
+        if (getConnectionLocked(handle->getToken()) == nullptr) {
             const bool noInputChannel =
                     info->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
             const bool canReceiveInput =
@@ -5101,13 +5314,14 @@
         }
 
         if (info->displayId != displayId) {
-            ALOGE("Window %s updated by wrong display %d, should belong to display %d",
-                  handle->getName().c_str(), displayId, info->displayId);
+            ALOGE("Window %s updated by wrong display %s, should belong to display %s",
+                  handle->getName().c_str(), displayId.toString().c_str(),
+                  info->displayId.toString().c_str());
             continue;
         }
 
         if ((oldHandlesById.find(handle->getId()) != oldHandlesById.end()) &&
-                (oldHandlesById.at(handle->getId())->getToken() == handle->getToken())) {
+            (oldHandlesById.at(handle->getId())->getToken() == handle->getToken())) {
             const sp<WindowInfoHandle>& oldHandle = oldHandlesById.at(handle->getId());
             oldHandle->updateFrom(handle);
             newHandles.push_back(oldHandle);
@@ -5128,7 +5342,8 @@
  * For removed handle, check if need to send a cancel event if already in touch.
  */
 void InputDispatcher::setInputWindowsLocked(
-        const std::vector<sp<WindowInfoHandle>>& windowInfoHandles, int32_t displayId) {
+        const std::vector<sp<WindowInfoHandle>>& windowInfoHandles,
+        ui::LogicalDisplayId displayId) {
     if (DEBUG_FOCUS) {
         std::string windowList;
         for (const sp<WindowInfoHandle>& iwh : windowInfoHandles) {
@@ -5136,6 +5351,7 @@
         }
         LOG(INFO) << "setInputWindows displayId=" << displayId << " " << windowList;
     }
+    ScopedSyntheticEventTracer traceContext(mTracer);
 
     // Check preconditions for new input windows
     for (const sp<WindowInfoHandle>& window : windowInfoHandles) {
@@ -5166,6 +5382,7 @@
 
     // Copy old handles for release if they are no longer present.
     const std::vector<sp<WindowInfoHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
+    const sp<WindowInfoHandle> removedFocusedWindowHandle = getFocusedWindowHandleLocked(displayId);
 
     updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId);
 
@@ -5174,37 +5391,35 @@
     std::optional<FocusResolver::FocusChanges> changes =
             mFocusResolver.setInputWindows(displayId, windowHandles);
     if (changes) {
-        onFocusChangedLocked(*changes);
+        onFocusChangedLocked(*changes, traceContext.getTracker(), removedFocusedWindowHandle);
     }
 
-    std::unordered_map<int32_t, TouchState>::iterator stateIt =
-            mTouchStatesByDisplay.find(displayId);
-    if (stateIt != mTouchStatesByDisplay.end()) {
-        TouchState& state = stateIt->second;
+    if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
+        TouchState& state = it->second;
         for (size_t i = 0; i < state.windows.size();) {
             TouchedWindow& touchedWindow = state.windows[i];
-            if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) {
-                LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
-                          << " in display %" << displayId;
-                std::shared_ptr<InputChannel> touchedInputChannel =
-                        getInputChannelLocked(touchedWindow.windowHandle->getToken());
-                if (touchedInputChannel != nullptr) {
-                    CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                               "touched window was removed");
-                    synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options);
-                    // Since we are about to drop the touch, cancel the events for the wallpaper as
-                    // well.
-                    if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
-                        touchedWindow.windowHandle->getInfo()->inputConfig.test(
-                                gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
-                        sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
-                        synthesizeCancelationEventsForWindowLocked(wallpaper, options);
+            if (getWindowHandleLocked(touchedWindow.windowHandle) != nullptr) {
+                i++;
+                continue;
+            }
+            LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
+                      << " in display %" << displayId;
+            CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+                                       "touched window was removed", traceContext.getTracker());
+            synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
+            // Since we are about to drop the touch, cancel the events for the wallpaper as
+            // well.
+            if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+                touchedWindow.windowHandle->getInfo()->inputConfig.test(
+                        gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+                for (const DeviceId deviceId : touchedWindow.getTouchingDeviceIds()) {
+                    if (const auto& ww = state.getWallpaperWindow(deviceId); ww != nullptr) {
+                        options.deviceId = deviceId;
+                        synthesizeCancelationEventsForWindowLocked(ww, options);
                     }
                 }
-                state.windows.erase(state.windows.begin() + i);
-            } else {
-                ++i;
             }
+            state.windows.erase(state.windows.begin() + i);
         }
 
         // If drag window is gone, it would receive a cancel event and broadcast the DRAG_END. We
@@ -5233,9 +5448,10 @@
 }
 
 void InputDispatcher::setFocusedApplication(
-        int32_t displayId, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
+        ui::LogicalDisplayId displayId,
+        const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
     if (DEBUG_FOCUS) {
-        ALOGD("setFocusedApplication displayId=%" PRId32 " %s", displayId,
+        ALOGD("setFocusedApplication displayId=%s %s", displayId.toString().c_str(),
               inputApplicationHandle ? inputApplicationHandle->getName().c_str() : "<nullptr>");
     }
     { // acquire lock
@@ -5248,7 +5464,8 @@
 }
 
 void InputDispatcher::setFocusedApplicationLocked(
-        int32_t displayId, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
+        ui::LogicalDisplayId displayId,
+        const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
     std::shared_ptr<InputApplicationHandle> oldFocusedApplicationHandle =
             getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
 
@@ -5268,6 +5485,14 @@
     resetNoFocusedWindowTimeoutLocked();
 }
 
+void InputDispatcher::setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) {
+    if (interval.count() < 0) {
+        LOG_ALWAYS_FATAL("Minimum time between user activity pokes should be >= 0");
+    }
+    std::scoped_lock _l(mLock);
+    mMinTimeBetweenUserActivityPokes = interval;
+}
+
 /**
  * Sets the focused display, which is responsible for receiving focus-dispatched input events where
  * the display not specified.
@@ -5277,26 +5502,29 @@
  * cancel all the unreleased display-unspecified events for the focused window on the old focused
  * display. The display-specified events won't be affected.
  */
-void InputDispatcher::setFocusedDisplay(int32_t displayId) {
+void InputDispatcher::setFocusedDisplay(ui::LogicalDisplayId displayId) {
     if (DEBUG_FOCUS) {
-        ALOGD("setFocusedDisplay displayId=%" PRId32, displayId);
+        ALOGD("setFocusedDisplay displayId=%s", displayId.toString().c_str());
     }
     { // acquire lock
         std::scoped_lock _l(mLock);
+        ScopedSyntheticEventTracer traceContext(mTracer);
 
         if (mFocusedDisplayId != displayId) {
             sp<IBinder> oldFocusedWindowToken =
                     mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
             if (oldFocusedWindowToken != nullptr) {
-                std::shared_ptr<InputChannel> inputChannel =
-                        getInputChannelLocked(oldFocusedWindowToken);
-                if (inputChannel != nullptr) {
-                    CancelationOptions
-                            options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
-                                    "The display which contains this window no longer has focus.");
-                    options.displayId = ADISPLAY_ID_NONE;
-                    synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
+                const auto windowHandle =
+                        getWindowHandleLocked(oldFocusedWindowToken, mFocusedDisplayId);
+                if (windowHandle == nullptr) {
+                    LOG(FATAL) << __func__ << ": Previously focused token did not have a window";
                 }
+                CancelationOptions
+                        options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
+                                "The display which contains this window no longer has focus.",
+                                traceContext.getTracker());
+                options.displayId = ui::LogicalDisplayId::INVALID;
+                synthesizeCancelationEventsForWindowLocked(windowHandle, options);
             }
             mFocusedDisplayId = displayId;
 
@@ -5305,7 +5533,8 @@
             sendFocusChangedCommandLocked(oldFocusedWindowToken, newFocusedWindowToken);
 
             if (newFocusedWindowToken == nullptr) {
-                ALOGW("Focused display #%" PRId32 " does not have a focused window.", displayId);
+                ALOGW("Focused display #%s does not have a focused window.",
+                      displayId.toString().c_str());
                 if (mFocusResolver.hasFocusedWindowTokens()) {
                     ALOGE("But another display has a focused window\n%s",
                           mFocusResolver.dumpFocusedWindows().c_str());
@@ -5371,15 +5600,15 @@
 }
 
 bool InputDispatcher::setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid,
-                                     bool hasPermission, int32_t displayId) {
+                                     bool hasPermission, ui::LogicalDisplayId displayId) {
     bool needWake = false;
     {
         std::scoped_lock lock(mLock);
         ALOGD_IF(DEBUG_TOUCH_MODE,
                  "Request to change touch mode to %s (calling pid=%s, uid=%s, "
-                 "hasPermission=%s, target displayId=%d, mTouchModePerDisplay[displayId]=%s)",
+                 "hasPermission=%s, target displayId=%s, mTouchModePerDisplay[displayId]=%s)",
                  toString(inTouchMode), pid.toString().c_str(), uid.toString().c_str(),
-                 toString(hasPermission), displayId,
+                 toString(hasPermission), displayId.toString().c_str(),
                  mTouchModePerDisplay.count(displayId) == 0
                          ? "not set"
                          : std::to_string(mTouchModePerDisplay[displayId]).c_str());
@@ -5437,7 +5666,7 @@
     mMaximumObscuringOpacityForTouch = opacity;
 }
 
-std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId /*displayId*/>
 InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) {
     for (auto& [displayId, state] : mTouchStatesByDisplay) {
         for (TouchedWindow& w : state.windows) {
@@ -5446,11 +5675,11 @@
             }
         }
     }
-    return std::make_tuple(nullptr, nullptr, ADISPLAY_ID_DEFAULT);
+    return std::make_tuple(nullptr, nullptr, ui::LogicalDisplayId::DEFAULT);
 }
 
-bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
-                                         bool isDragDrop) {
+bool InputDispatcher::transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
+                                           bool isDragDrop) {
     if (fromToken == toToken) {
         if (DEBUG_FOCUS) {
             ALOGD("Trivial transfer to same window.");
@@ -5468,30 +5697,30 @@
             ALOGD("Touch transfer failed because from window is not being touched.");
             return false;
         }
-        std::set<int32_t> deviceIds = touchedWindow->getTouchingDeviceIds();
+        std::set<DeviceId> deviceIds = touchedWindow->getTouchingDeviceIds();
         if (deviceIds.size() != 1) {
             LOG(INFO) << "Can't transfer touch. Currently touching devices: " << dumpSet(deviceIds)
                       << " for window: " << touchedWindow->dump();
             return false;
         }
-        const int32_t deviceId = *deviceIds.begin();
+        const DeviceId deviceId = *deviceIds.begin();
 
-        sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId);
-        if (toWindowHandle == nullptr) {
-            ALOGW("Cannot transfer touch because to window not found.");
+        const sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
+        const sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId);
+        if (!toWindowHandle) {
+            ALOGW("Cannot transfer touch because the transfer target window was not found.");
             return false;
         }
 
         if (DEBUG_FOCUS) {
-            ALOGD("transferTouchFocus: fromWindowHandle=%s, toWindowHandle=%s",
+            ALOGD("%s: fromWindowHandle=%s, toWindowHandle=%s", __func__,
                   touchedWindow->windowHandle->getName().c_str(),
                   toWindowHandle->getName().c_str());
         }
 
         // Erase old window.
         ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags;
-        std::bitset<MAX_POINTER_ID + 1> pointerIds = touchedWindow->getTouchingPointers(deviceId);
-        sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
+        std::vector<PointerProperties> pointers = touchedWindow->getTouchingPointers(deviceId);
         state->removeWindowByToken(fromToken);
 
         // Add new window.
@@ -5501,35 +5730,45 @@
         if (canReceiveForegroundTouches(*toWindowHandle->getInfo())) {
             newTargetFlags |= InputTarget::Flags::FOREGROUND;
         }
+        // Transferring touch focus using this API should not effect the focused window.
+        newTargetFlags |= InputTarget::Flags::NO_FOCUS_CHANGE;
         state->addOrUpdateWindow(toWindowHandle, InputTarget::DispatchMode::AS_IS, newTargetFlags,
-                                 deviceId, pointerIds, downTimeInTarget);
+                                 deviceId, pointers, downTimeInTarget);
 
         // Store the dragging window.
         if (isDragDrop) {
-            if (pointerIds.count() != 1) {
+            if (pointers.size() != 1) {
                 ALOGW("The drag and drop cannot be started when there is no pointer or more than 1"
                       " pointer on the window.");
                 return false;
             }
             // Track the pointer id for drag window and generate the drag state.
-            const size_t id = firstMarkedBit(pointerIds);
+            const size_t id = pointers.begin()->id;
             mDragState = std::make_unique<DragState>(toWindowHandle, id);
         }
 
         // Synthesize cancel for old window and down for new window.
+        ScopedSyntheticEventTracer traceContext(mTracer);
         std::shared_ptr<Connection> fromConnection = getConnectionLocked(fromToken);
         std::shared_ptr<Connection> toConnection = getConnectionLocked(toToken);
         if (fromConnection != nullptr && toConnection != nullptr) {
             fromConnection->inputState.mergePointerStateTo(toConnection->inputState);
             CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                       "transferring touch from this window to another window");
-            synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
-            synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
-                                                           newTargetFlags);
+                                       "transferring touch from this window to another window",
+                                       traceContext.getTracker());
+            synthesizeCancelationEventsForWindowLocked(fromWindowHandle, options, fromConnection);
 
             // Check if the wallpaper window should deliver the corresponding event.
             transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
-                                   *state, deviceId, pointerIds);
+                                   *state, deviceId, pointers, traceContext.getTracker());
+
+            // Because new window may have a wallpaper window, it will merge input state from it
+            // parent window, after this the firstNewPointerIdx in input state will be reset, then
+            // it will cause new move event be thought inconsistent, so we should synthesize the
+            // down event after it reset.
+            synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
+                                                           newTargetFlags,
+                                                           traceContext.getTracker());
         }
     } // release lock
 
@@ -5543,10 +5782,11 @@
  * Return null if there are no windows touched on that display, or if more than one foreground
  * window is being touched.
  */
-sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(int32_t displayId) const {
+sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(
+        ui::LogicalDisplayId displayId) const {
     auto stateIt = mTouchStatesByDisplay.find(displayId);
     if (stateIt == mTouchStatesByDisplay.end()) {
-        ALOGI("No touch state on display %" PRId32, displayId);
+        ALOGI("No touch state on display %s", displayId.toString().c_str());
         return nullptr;
     }
 
@@ -5568,14 +5808,15 @@
 }
 
 // Binder call
-bool InputDispatcher::transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) {
+bool InputDispatcher::transferTouchOnDisplay(const sp<IBinder>& destChannelToken,
+                                             ui::LogicalDisplayId displayId) {
     sp<IBinder> fromToken;
     { // acquire lock
         std::scoped_lock _l(mLock);
         sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(destChannelToken, displayId);
         if (toWindowHandle == nullptr) {
-            ALOGW("Could not find window associated with token=%p on display %" PRId32,
-                  destChannelToken.get(), displayId);
+            ALOGW("Could not find window associated with token=%p on display %s",
+                  destChannelToken.get(), displayId.toString().c_str());
             return false;
         }
 
@@ -5588,7 +5829,7 @@
         fromToken = from->getToken();
     } // release lock
 
-    return transferTouchFocus(fromToken, destChannelToken);
+    return transferTouchGesture(fromToken, destChannelToken);
 }
 
 void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
@@ -5596,7 +5837,9 @@
         ALOGD("Resetting and dropping all events (%s).", reason);
     }
 
-    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason);
+    ScopedSyntheticEventTracer traceContext(mTracer);
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason,
+                               traceContext.getTracker());
     synthesizeCancelationEventsForAllConnectionsLocked(options);
 
     resetKeyRepeatLocked();
@@ -5624,7 +5867,7 @@
     std::string dump;
 
     dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n",
-                         toString(mCurrentPointerCaptureRequest.enable));
+                         toString(mCurrentPointerCaptureRequest.isEnable()));
 
     std::string windowName = "None";
     if (mWindowTokenWithPointerCapture) {
@@ -5642,18 +5885,19 @@
     dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled));
     dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen));
     dump += StringPrintf(INDENT "InputFilterEnabled: %s\n", toString(mInputFilterEnabled));
-    dump += StringPrintf(INDENT "FocusedDisplayId: %" PRId32 "\n", mFocusedDisplayId);
+    dump += StringPrintf(INDENT "FocusedDisplayId: %s\n", mFocusedDisplayId.toString().c_str());
 
     if (!mFocusedApplicationHandlesByDisplay.empty()) {
         dump += StringPrintf(INDENT "FocusedApplications:\n");
         for (auto& it : mFocusedApplicationHandlesByDisplay) {
-            const int32_t displayId = it.first;
+            const ui::LogicalDisplayId displayId = it.first;
             const std::shared_ptr<InputApplicationHandle>& applicationHandle = it.second;
             const std::chrono::duration timeout =
                     applicationHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
-            dump += StringPrintf(INDENT2 "displayId=%" PRId32
-                                         ", name='%s', dispatchingTimeout=%" PRId64 "ms\n",
-                                 displayId, applicationHandle->getName().c_str(), millis(timeout));
+            dump += StringPrintf(INDENT2 "displayId=%s, name='%s', dispatchingTimeout=%" PRId64
+                                         "ms\n",
+                                 displayId.toString().c_str(), applicationHandle->getName().c_str(),
+                                 millis(timeout));
         }
     } else {
         dump += StringPrintf(INDENT "FocusedApplications: <none>\n");
@@ -5666,7 +5910,7 @@
         dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
         for (const auto& [displayId, state] : mTouchStatesByDisplay) {
             std::string touchStateDump = addLinePrefix(state.dump(), INDENT2);
-            dump += INDENT2 + std::to_string(displayId) + " : " + touchStateDump;
+            dump += INDENT2 + displayId.toString() + " : " + touchStateDump;
         }
     } else {
         dump += INDENT "TouchStates: <no displays touched>\n";
@@ -5679,7 +5923,7 @@
 
     if (!mWindowHandlesByDisplay.empty()) {
         for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
-            dump += StringPrintf(INDENT "Display: %" PRId32 "\n", displayId);
+            dump += StringPrintf(INDENT "Display: %s\n", displayId.toString().c_str());
             if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
                 const auto& displayInfo = it->second;
                 dump += StringPrintf(INDENT2 "logicalSize=%dx%d\n", displayInfo.logicalWidth,
@@ -5705,7 +5949,8 @@
 
     if (!mGlobalMonitorsByDisplay.empty()) {
         for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
-            dump += StringPrintf(INDENT "Global monitors on display %d:\n", displayId);
+            dump += StringPrintf(INDENT "Global monitors on display %s:\n",
+                                 displayId.toString().c_str());
             dumpMonitors(dump, monitors);
         }
     } else {
@@ -5758,11 +6003,10 @@
     if (!mConnectionsByToken.empty()) {
         dump += INDENT "Connections:\n";
         for (const auto& [token, connection] : mConnectionsByToken) {
-            dump += StringPrintf(INDENT2 "%i: channelName='%s', windowName='%s', "
+            dump += StringPrintf(INDENT2 "%i: channelName='%s', "
                                          "status=%s, monitor=%s, responsive=%s\n",
-                                 connection->inputChannel->getFd().get(),
+                                 connection->inputPublisher.getChannel().getFd(),
                                  connection->getInputChannelName().c_str(),
-                                 connection->getWindowName().c_str(),
                                  ftl::enum_string(connection->status).c_str(),
                                  toString(connection->monitor), toString(connection->responsive));
 
@@ -5792,21 +6036,11 @@
         dump += INDENT "Connections: <none>\n";
     }
 
-    dump += "input_flags::remove_app_switch_drops() = ";
-    dump += toString(REMOVE_APP_SWITCH_DROPS);
-    dump += "\n";
-    if (isAppSwitchPendingLocked()) {
-        dump += StringPrintf(INDENT "AppSwitch: pending, due in %" PRId64 "ms\n",
-                             ns2ms(mAppSwitchDueTime - now()));
-    } else {
-        dump += INDENT "AppSwitch: not pending\n";
-    }
-
     if (!mTouchModePerDisplay.empty()) {
         dump += INDENT "TouchModePerDisplay:\n";
         for (const auto& [displayId, touchMode] : mTouchModePerDisplay) {
-            dump += StringPrintf(INDENT2 "Display: %" PRId32 " TouchMode: %s\n", displayId,
-                                 std::to_string(touchMode).c_str());
+            dump += StringPrintf(INDENT2 "Display: %s TouchMode: %s\n",
+                                 displayId.toString().c_str(), std::to_string(touchMode).c_str());
         }
     } else {
         dump += INDENT "TouchModePerDisplay: <none>\n";
@@ -5818,14 +6052,16 @@
                          ns2ms(mConfig.keyRepeatTimeout));
     dump += mLatencyTracker.dump(INDENT2);
     dump += mLatencyAggregator.dump(INDENT2);
+    dump += INDENT "InputTracer: ";
+    dump += mTracer == nullptr ? "Disabled" : "Enabled";
 }
 
 void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) const {
     const size_t numMonitors = monitors.size();
     for (size_t i = 0; i < numMonitors; i++) {
         const Monitor& monitor = monitors[i];
-        const std::shared_ptr<InputChannel>& channel = monitor.inputChannel;
-        dump += StringPrintf(INDENT2 "%zu: '%s', ", i, channel->getName().c_str());
+        const std::shared_ptr<Connection>& connection = monitor.connection;
+        dump += StringPrintf(INDENT2 "%zu: '%s', ", i, connection->getInputChannelName().c_str());
         dump += "\n";
     }
 }
@@ -5855,20 +6091,20 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
-        auto&& fd = serverChannel->getFd();
+        const int fd = serverChannel->getFd();
         std::shared_ptr<Connection> connection =
                 std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/false,
                                              mIdGenerator);
 
-        if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
+        auto [_, inserted] = mConnectionsByToken.try_emplace(token, connection);
+        if (!inserted) {
             ALOGE("Created a new connection, but the token %p is already known", token.get());
         }
-        mConnectionsByToken.emplace(token, connection);
 
         std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                             this, std::placeholders::_1, token);
 
-        mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
+        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
                        nullptr);
     } // release lock
 
@@ -5877,12 +6113,11 @@
     return clientChannel;
 }
 
-Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_t displayId,
-                                                                          const std::string& name,
-                                                                          gui::Pid pid) {
-    std::shared_ptr<InputChannel> serverChannel;
+Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(
+        ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) {
+    std::unique_ptr<InputChannel> serverChannel;
     std::unique_ptr<InputChannel> clientChannel;
-    status_t result = openInputChannelPair(name, serverChannel, clientChannel);
+    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
     if (result) {
         return base::Error(result) << "Failed to open input channel pair with name " << name;
     }
@@ -5890,26 +6125,28 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
 
-        if (displayId < 0) {
+        if (displayId < ui::LogicalDisplayId::DEFAULT) {
             return base::Error(BAD_VALUE) << "Attempted to create input monitor with name " << name
                                           << " without a specified display.";
         }
 
-        std::shared_ptr<Connection> connection =
-                std::make_shared<Connection>(serverChannel, /*monitor=*/true, mIdGenerator);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
-        auto&& fd = serverChannel->getFd();
+        const int fd = serverChannel->getFd();
+        std::shared_ptr<Connection> connection =
+                std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/true,
+                                             mIdGenerator);
 
-        if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
+        auto [_, inserted] = mConnectionsByToken.emplace(token, connection);
+        if (!inserted) {
             ALOGE("Created a new connection, but the token %p is already known", token.get());
         }
-        mConnectionsByToken.emplace(token, connection);
+
         std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                             this, std::placeholders::_1, token);
 
-        mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid);
+        mGlobalMonitorsByDisplay[displayId].emplace_back(connection, pid);
 
-        mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
+        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
                        nullptr);
     }
 
@@ -5948,7 +6185,7 @@
         removeMonitorChannelLocked(connectionToken);
     }
 
-    mLooper->removeFd(connection->inputChannel->getFd().get());
+    mLooper->removeFd(connection->inputPublisher.getChannel().getFd());
 
     nsecs_t currentTime = now();
     abortBrokenDispatchCycleLocked(currentTime, connection, notify);
@@ -5961,7 +6198,7 @@
     for (auto it = mGlobalMonitorsByDisplay.begin(); it != mGlobalMonitorsByDisplay.end();) {
         auto& [displayId, monitors] = *it;
         std::erase_if(monitors, [connectionToken](const Monitor& monitor) {
-            return monitor.inputChannel->getConnectionToken() == connectionToken;
+            return monitor.connection->getToken() == connectionToken;
         });
 
         if (monitors.empty()) {
@@ -5978,8 +6215,8 @@
 }
 
 status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
-    const std::shared_ptr<InputChannel> requestingChannel = getInputChannelLocked(token);
-    if (!requestingChannel) {
+    const std::shared_ptr<Connection> requestingConnection = getConnectionLocked(token);
+    if (!requestingConnection) {
         LOG(WARNING)
                 << "Attempted to pilfer pointers from an un-registered channel or invalid token";
         return BAD_VALUE;
@@ -5998,28 +6235,29 @@
         return BAD_VALUE;
     }
 
+    ScopedSyntheticEventTracer traceContext(mTracer);
     for (const DeviceId deviceId : deviceIds) {
         TouchState& state = *statePtr;
         TouchedWindow& window = *windowPtr;
         // Send cancel events to all the input channels we're stealing from.
         CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "input channel stole pointer stream");
+                                   "input channel stole pointer stream", traceContext.getTracker());
         options.deviceId = deviceId;
         options.displayId = displayId;
-        std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
+        std::vector<PointerProperties> pointers = window.getTouchingPointers(deviceId);
+        std::bitset<MAX_POINTER_ID + 1> pointerIds = getPointerIds(pointers);
         options.pointerIds = pointerIds;
+
         std::string canceledWindows;
         for (const TouchedWindow& w : state.windows) {
-            const std::shared_ptr<InputChannel> channel =
-                    getInputChannelLocked(w.windowHandle->getToken());
-            if (channel != nullptr && channel->getConnectionToken() != token) {
-                synthesizeCancelationEventsForInputChannelLocked(channel, options);
+            if (w.windowHandle->getToken() != token) {
+                synthesizeCancelationEventsForWindowLocked(w.windowHandle, options);
                 canceledWindows += canceledWindows.empty() ? "[" : ", ";
-                canceledWindows += channel->getName();
+                canceledWindows += w.windowHandle->getName();
             }
         }
         canceledWindows += canceledWindows.empty() ? "[]" : "]";
-        LOG(INFO) << "Channel " << requestingChannel->getName()
+        LOG(INFO) << "Channel " << requestingConnection->getInputChannelName()
                   << " is stealing input gesture for device " << deviceId << " from "
                   << canceledWindows;
 
@@ -6049,7 +6287,7 @@
             return;
         }
 
-        if (enabled == mCurrentPointerCaptureRequest.enable) {
+        if (enabled == mCurrentPointerCaptureRequest.isEnable()) {
             ALOGW("Ignoring request to %s Pointer Capture: "
                   "window has %s requested pointer capture.",
                   enabled ? "enable" : "disable", enabled ? "already" : "not");
@@ -6065,14 +6303,15 @@
             }
         }
 
-        setPointerCaptureLocked(enabled);
+        setPointerCaptureLocked(enabled ? windowToken : nullptr);
     } // release lock
 
     // Wake the thread to process command entries.
     mLooper->wake();
 }
 
-void InputDispatcher::setDisplayEligibilityForPointerCapture(int32_t displayId, bool isEligible) {
+void InputDispatcher::setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId,
+                                                             bool isEligible) {
     { // acquire lock
         std::scoped_lock _l(mLock);
         std::erase(mIneligibleDisplaysForPointerCapture, displayId);
@@ -6085,7 +6324,7 @@
 std::optional<gui::Pid> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) {
     for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
         for (const Monitor& monitor : monitors) {
-            if (monitor.inputChannel->getConnectionToken() == token) {
+            if (monitor.connection->getToken() == token) {
                 return monitor.pid;
             }
         }
@@ -6117,8 +6356,8 @@
 }
 
 void InputDispatcher::removeConnectionLocked(const std::shared_ptr<Connection>& connection) {
-    mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
-    mConnectionsByToken.erase(connection->inputChannel->getConnectionToken());
+    mAnrTracker.eraseToken(connection->getToken());
+    mConnectionsByToken.erase(connection->getToken());
 }
 
 void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
@@ -6140,12 +6379,11 @@
 
         const nsecs_t eventDuration = finishTime - dispatchEntry.deliveryTime;
         if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
-            ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
+            ALOGI("%s spent %" PRId64 "ms processing %s", connection->getInputChannelName().c_str(),
                   ns2ms(eventDuration), dispatchEntry.eventEntry->getDescription().c_str());
         }
         if (shouldReportFinishedEvent(dispatchEntry, *connection)) {
-            mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id,
-                                               connection->inputChannel->getConnectionToken(),
+            mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id, connection->getToken(),
                                                dispatchEntry.deliveryTime, consumeTime, finishTime);
         }
 
@@ -6166,7 +6404,7 @@
         std::unique_ptr<DispatchEntry> dispatchEntry = std::move(*entryIt);
         connection->waitQueue.erase(entryIt);
 
-        const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
+        const sp<IBinder>& connectionToken = connection->getToken();
         mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
         if (!connection->responsive) {
             connection->responsive = isConnectionResponsive(*connection);
@@ -6177,9 +6415,18 @@
         }
         traceWaitQueueLength(*connection);
         if (fallbackKeyEntry && connection->status == Connection::Status::NORMAL) {
-            const InputTarget target{.inputChannel = connection->inputChannel,
-                                     .flags = dispatchEntry->targetFlags};
-            enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry), target);
+            const auto windowHandle = getWindowHandleLocked(connection->getToken());
+            // Only dispatch fallbacks if there is a window for the connection.
+            if (windowHandle != nullptr) {
+                const auto inputTarget =
+                        createInputTargetLocked(windowHandle, InputTarget::DispatchMode::AS_IS,
+                                                dispatchEntry->targetFlags,
+                                                fallbackKeyEntry->downTime);
+                if (inputTarget.has_value()) {
+                    enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry),
+                                               *inputTarget);
+                }
+            }
         }
         releaseDispatchEntry(std::move(dispatchEntry));
     }
@@ -6213,7 +6460,7 @@
     // is already healthy again. Don't raise ANR in this situation
     if (connection->waitQueue.empty()) {
         ALOGI("Not raising ANR because the connection %s has recovered",
-              connection->inputChannel->getName().c_str());
+              connection->getInputChannelName().c_str());
         return;
     }
     /**
@@ -6228,10 +6475,10 @@
     const nsecs_t currentWait = now() - oldestEntry.deliveryTime;
     std::string reason =
             android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
-                                        connection->inputChannel->getName().c_str(),
+                                        connection->getInputChannelName().c_str(),
                                         ns2ms(currentWait),
                                         oldestEntry.eventEntry->getDescription().c_str());
-    sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
+    sp<IBinder> connectionToken = connection->getToken();
     updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
 
     processConnectionUnresponsiveLocked(*connection, std::move(reason));
@@ -6330,15 +6577,15 @@
  */
 void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection,
                                                           std::string reason) {
-    const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
+    const sp<IBinder>& connectionToken = connection.getToken();
     std::optional<gui::Pid> pid;
     if (connection.monitor) {
-        ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
+        ALOGW("Monitor %s is unresponsive: %s", connection.getInputChannelName().c_str(),
               reason.c_str());
         pid = findMonitorPidByTokenLocked(connectionToken);
     } else {
         // The connection is a window
-        ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
+        ALOGW("Window %s is unresponsive: %s", connection.getInputChannelName().c_str(),
               reason.c_str());
         const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken);
         if (handle != nullptr) {
@@ -6352,7 +6599,7 @@
  * Tell the policy that a connection has become responsive so that it can stop ANR.
  */
 void InputDispatcher::processConnectionResponsiveLocked(const Connection& connection) {
-    const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
+    const sp<IBinder>& connectionToken = connection.getToken();
     std::optional<gui::Pid> pid;
     if (connection.monitor) {
         pid = findMonitorPidByTokenLocked(connectionToken);
@@ -6403,22 +6650,27 @@
             mLock.unlock();
 
             if (const auto unhandledKeyFallback =
-                        mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
-                                                     event, keyEntry.policyFlags);
+                        mPolicy.dispatchUnhandledKey(connection->getToken(), event,
+                                                     keyEntry.policyFlags);
                 unhandledKeyFallback) {
                 event = *unhandledKeyFallback;
             }
 
             mLock.lock();
 
-            // Cancel the fallback key.
+            // Cancel the fallback key, but only if we still have a window for the channel.
+            // It could have been removed during the policy call.
             if (*fallbackKeyCode != AKEYCODE_UNKNOWN) {
-                CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
-                                           "application handled the original non-fallback key "
-                                           "or is no longer a foreground target, "
-                                           "canceling previously dispatched fallback key");
-                options.keyCode = *fallbackKeyCode;
-                synthesizeCancelationEventsForConnectionLocked(connection, options);
+                const auto windowHandle = getWindowHandleLocked(connection->getToken());
+                if (windowHandle != nullptr) {
+                    CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
+                                               "application handled the original non-fallback key "
+                                               "or is no longer a foreground target, "
+                                               "canceling previously dispatched fallback key",
+                                               keyEntry.traceTracker);
+                    options.keyCode = *fallbackKeyCode;
+                    synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection);
+                }
             }
             connection->inputState.removeFallbackKey(originalKeyCode);
         }
@@ -6448,8 +6700,8 @@
         mLock.unlock();
 
         bool fallback = false;
-        if (auto fb = mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
-                                                   event, keyEntry.policyFlags);
+        if (auto fb = mPolicy.dispatchUnhandledKey(connection->getToken(), event,
+                                                   keyEntry.policyFlags);
             fb) {
             fallback = true;
             event = *fb;
@@ -6494,10 +6746,14 @@
                 }
             }
 
-            CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
-                                       "canceling fallback, policy no longer desires it");
-            options.keyCode = *fallbackKeyCode;
-            synthesizeCancelationEventsForConnectionLocked(connection, options);
+            const auto windowHandle = getWindowHandleLocked(connection->getToken());
+            if (windowHandle != nullptr) {
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
+                                           "canceling fallback, policy no longer desires it",
+                                           keyEntry.traceTracker);
+                options.keyCode = *fallbackKeyCode;
+                synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection);
+            }
 
             fallback = false;
             *fallbackKeyCode = AKEYCODE_UNKNOWN;
@@ -6530,6 +6786,10 @@
                                                *fallbackKeyCode, event.getScanCode(),
                                                event.getMetaState(), event.getRepeatCount(),
                                                event.getDownTime());
+            if (mTracer) {
+                newEntry->traceTracker =
+                        mTracer->traceDerivedEvent(*newEntry, *keyEntry.traceTracker);
+            }
             if (DEBUG_OUTBOUND_EVENT_DETAILS) {
                 ALOGD("Unhandled key event: Dispatching fallback key.  "
                       "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
@@ -6557,7 +6817,8 @@
 void InputDispatcher::traceOutboundQueueLength(const Connection& connection) {
     if (ATRACE_ENABLED()) {
         char counterName[40];
-        snprintf(counterName, sizeof(counterName), "oq:%s", connection.getWindowName().c_str());
+        snprintf(counterName, sizeof(counterName), "oq:%s",
+                 connection.getInputChannelName().c_str());
         ATRACE_INT(counterName, connection.outboundQueue.size());
     }
 }
@@ -6565,7 +6826,8 @@
 void InputDispatcher::traceWaitQueueLength(const Connection& connection) {
     if (ATRACE_ENABLED()) {
         char counterName[40];
-        snprintf(counterName, sizeof(counterName), "wq:%s", connection.getWindowName().c_str());
+        snprintf(counterName, sizeof(counterName), "wq:%s",
+                 connection.getInputChannelName().c_str());
         ATRACE_INT(counterName, connection.waitQueue.size());
     }
 }
@@ -6625,24 +6887,33 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         std::optional<FocusResolver::FocusChanges> changes =
-                mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId));
+                mFocusResolver.setFocusedWindow(request,
+                                                getWindowHandlesLocked(
+                                                        ui::LogicalDisplayId{request.displayId}));
+        ScopedSyntheticEventTracer traceContext(mTracer);
         if (changes) {
-            onFocusChangedLocked(*changes);
+            onFocusChangedLocked(*changes, traceContext.getTracker());
         }
     } // release lock
     // Wake up poll loop since it may need to make new input dispatching choices.
     mLooper->wake();
 }
 
-void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes) {
+void InputDispatcher::onFocusChangedLocked(
+        const FocusResolver::FocusChanges& changes,
+        const std::unique_ptr<trace::EventTrackerInterface>& traceTracker,
+        const sp<WindowInfoHandle> removedFocusedWindowHandle) {
     if (changes.oldFocus) {
-        std::shared_ptr<InputChannel> focusedInputChannel = getInputChannelLocked(changes.oldFocus);
-        if (focusedInputChannel) {
-            CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
-                                       "focus left window");
-            synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
-            enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason);
+        const auto resolvedWindow = removedFocusedWindowHandle != nullptr
+                ? removedFocusedWindowHandle
+                : getWindowHandleLocked(changes.oldFocus, changes.displayId);
+        if (resolvedWindow == nullptr) {
+            LOG(FATAL) << __func__ << ": Previously focused token did not have a window";
         }
+        CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
+                                   "focus left window", traceTracker);
+        synthesizeCancelationEventsForWindowLocked(resolvedWindow, options);
+        enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason);
     }
     if (changes.newFocus) {
         resetNoFocusedWindowTimeoutLocked();
@@ -6665,14 +6936,14 @@
 }
 
 void InputDispatcher::disablePointerCaptureForcedLocked() {
-    if (!mCurrentPointerCaptureRequest.enable && !mWindowTokenWithPointerCapture) {
+    if (!mCurrentPointerCaptureRequest.isEnable() && !mWindowTokenWithPointerCapture) {
         return;
     }
 
     ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus.");
 
-    if (mCurrentPointerCaptureRequest.enable) {
-        setPointerCaptureLocked(false);
+    if (mCurrentPointerCaptureRequest.isEnable()) {
+        setPointerCaptureLocked(nullptr);
     }
 
     if (!mWindowTokenWithPointerCapture) {
@@ -6692,8 +6963,8 @@
     mInboundQueue.push_front(std::move(entry));
 }
 
-void InputDispatcher::setPointerCaptureLocked(bool enable) {
-    mCurrentPointerCaptureRequest.enable = enable;
+void InputDispatcher::setPointerCaptureLocked(const sp<IBinder>& windowToken) {
+    mCurrentPointerCaptureRequest.window = windowToken;
     mCurrentPointerCaptureRequest.seq++;
     auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
@@ -6702,7 +6973,7 @@
     postCommandLocked(std::move(command));
 }
 
-void InputDispatcher::displayRemoved(int32_t displayId) {
+void InputDispatcher::displayRemoved(ui::LogicalDisplayId displayId) {
     { // acquire lock
         std::scoped_lock _l(mLock);
         // Set an empty list to remove all handles from the specific display.
@@ -6716,6 +6987,7 @@
         // Remove the associated touch mode state.
         mTouchModePerDisplay.erase(displayId);
         mVerifiersByDisplay.erase(displayId);
+        mInputFilterVerifiersByDisplay.erase(displayId);
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
@@ -6723,9 +6995,18 @@
 }
 
 void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) {
+    if (auto result = validateWindowInfosUpdate(update); !result.ok()) {
+        {
+            // acquire lock
+            std::scoped_lock _l(mLock);
+            logDispatchStateLocked();
+        }
+        LOG_ALWAYS_FATAL("Incorrect WindowInfosUpdate provided: %s",
+                         result.error().message().c_str());
+    };
     // The listener sends the windows as a flattened array. Separate the windows by display for
     // more convenient parsing.
-    std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
+    std::unordered_map<ui::LogicalDisplayId, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
     for (const auto& info : update.windowInfos) {
         handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());
         handlesPerDisplay[info.displayId].push_back(sp<WindowInfoHandle>::make(info));
@@ -6767,10 +7048,10 @@
                  WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED) &&
          isWindowObscuredLocked(windowHandle))) {
         ALOGW("Dropping %s event targeting %s as requested by the input configuration {%s} on "
-              "display %" PRId32 ".",
+              "display %s.",
               ftl::enum_string(entry.type).c_str(), windowHandle->getName().c_str(),
               windowHandle->getInfo()->inputConfig.string().c_str(),
-              windowHandle->getInfo()->displayId);
+              windowHandle->getInfo()->displayId.toString().c_str());
         return true;
     }
     return false;
@@ -6784,9 +7065,10 @@
 void InputDispatcher::cancelCurrentTouch() {
     {
         std::scoped_lock _l(mLock);
+        ScopedSyntheticEventTracer traceContext(mTracer);
         ALOGD("Canceling all ongoing pointer gestures on all displays.");
         CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "cancel current touch");
+                                   "cancel current touch", traceContext.getTracker());
         synthesizeCancelationEventsForAllConnectionsLocked(options);
 
         mTouchStatesByDisplay.clear();
@@ -6803,17 +7085,17 @@
 void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                                          const sp<WindowInfoHandle>& oldWindowHandle,
                                          const sp<WindowInfoHandle>& newWindowHandle,
-                                         TouchState& state, int32_t deviceId, int32_t pointerId,
+                                         TouchState& state, DeviceId deviceId,
+                                         const PointerProperties& pointerProperties,
                                          std::vector<InputTarget>& targets) const {
-    std::bitset<MAX_POINTER_ID + 1> pointerIds;
-    pointerIds.set(pointerId);
+    std::vector<PointerProperties> pointers{pointerProperties};
     const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
     const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) &&
             newWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
     const sp<WindowInfoHandle> oldWallpaper =
-            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+            oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
             newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
@@ -6823,25 +7105,25 @@
     if (oldWallpaper != nullptr) {
         const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
         addPointerWindowTargetLocked(oldWallpaper, InputTarget::DispatchMode::SLIPPERY_EXIT,
-                                     oldTouchedWindow.targetFlags, pointerIds,
+                                     oldTouchedWindow.targetFlags, getPointerIds(pointers),
                                      oldTouchedWindow.getDownTimeInTarget(deviceId), targets);
-        state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper);
+        state.removeTouchingPointerFromWindow(deviceId, pointerProperties.id, oldWallpaper);
     }
 
     if (newWallpaper != nullptr) {
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::SLIPPERY_ENTER,
                                 InputTarget::Flags::WINDOW_IS_OBSCURED |
                                         InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED,
-                                deviceId, pointerIds);
+                                deviceId, pointers);
     }
 }
 
-void InputDispatcher::transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
-                                             ftl::Flags<InputTarget::Flags> newTargetFlags,
-                                             const sp<WindowInfoHandle> fromWindowHandle,
-                                             const sp<WindowInfoHandle> toWindowHandle,
-                                             TouchState& state, int32_t deviceId,
-                                             std::bitset<MAX_POINTER_ID + 1> pointerIds) {
+void InputDispatcher::transferWallpaperTouch(
+        ftl::Flags<InputTarget::Flags> oldTargetFlags,
+        ftl::Flags<InputTarget::Flags> newTargetFlags, const sp<WindowInfoHandle> fromWindowHandle,
+        const sp<WindowInfoHandle> toWindowHandle, TouchState& state, DeviceId deviceId,
+        const std::vector<PointerProperties>& pointers,
+        const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) {
     const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
             fromWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
@@ -6850,7 +7132,7 @@
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
 
     const sp<WindowInfoHandle> oldWallpaper =
-            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+            oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
             newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
@@ -6859,18 +7141,19 @@
 
     if (oldWallpaper != nullptr) {
         CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "transferring touch focus to another window");
+                                   "transferring touch focus to another window", traceTracker);
         state.removeWindowByToken(oldWallpaper->getToken());
         synthesizeCancelationEventsForWindowLocked(oldWallpaper, options);
     }
 
     if (newWallpaper != nullptr) {
         nsecs_t downTimeInTarget = now();
-        ftl::Flags<InputTarget::Flags> wallpaperFlags = oldTargetFlags & InputTarget::Flags::SPLIT;
+        ftl::Flags<InputTarget::Flags> wallpaperFlags = newTargetFlags;
+        wallpaperFlags |= oldTargetFlags & InputTarget::Flags::SPLIT;
         wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED |
                 InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::AS_IS, wallpaperFlags,
-                                deviceId, pointerIds, downTimeInTarget);
+                                deviceId, pointers, downTimeInTarget);
         std::shared_ptr<Connection> wallpaperConnection =
                 getConnectionLocked(newWallpaper->getToken());
         if (wallpaperConnection != nullptr) {
@@ -6878,7 +7161,7 @@
                     getConnectionLocked(toWindowHandle->getToken());
             toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
             synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection,
-                                                           wallpaperFlags);
+                                                           wallpaperFlags, traceTracker);
         }
     }
 }
@@ -6912,8 +7195,9 @@
     mConfig.keyRepeatDelay = delay.count();
 }
 
-bool InputDispatcher::isPointerInWindow(const sp<android::IBinder>& token, int32_t displayId,
-                                        DeviceId deviceId, int32_t pointerId) {
+bool InputDispatcher::isPointerInWindow(const sp<android::IBinder>& token,
+                                        ui::LogicalDisplayId displayId, DeviceId deviceId,
+                                        int32_t pointerId) {
     std::scoped_lock _l(mLock);
     auto touchStateIt = mTouchStatesByDisplay.find(displayId);
     if (touchStateIt == mTouchStatesByDisplay.end()) {
@@ -6929,4 +7213,11 @@
     return false;
 }
 
+void InputDispatcher::setInputMethodConnectionIsActive(bool isActive) {
+    std::scoped_lock _l(mLock);
+    if (mTracer) {
+        mTracer->setInputMethodConnectionIsActive(isActive);
+    }
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 3f99b2d..6240e7f 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -32,6 +32,8 @@
 #include "Monitor.h"
 #include "TouchState.h"
 #include "TouchedWindow.h"
+#include "trace/InputTracerInterface.h"
+#include "trace/InputTracingBackendInterface.h"
 
 #include <attestation/HmacKeyManager.h>
 #include <gui/InputApplication.h>
@@ -39,6 +41,7 @@
 #include <input/Input.h>
 #include <input/InputTransport.h>
 #include <limits.h>
+#include <powermanager/PowerManager.h>
 #include <stddef.h>
 #include <unistd.h>
 #include <utils/BitSet.h>
@@ -82,6 +85,9 @@
     static constexpr bool kDefaultInTouchMode = true;
 
     explicit InputDispatcher(InputDispatcherPolicyInterface& policy);
+    // Constructor used for testing.
+    explicit InputDispatcher(InputDispatcherPolicyInterface&,
+                             std::unique_ptr<trace::InputTracingBackendInterface>);
     ~InputDispatcher() override;
 
     void dump(std::string& dump) const override;
@@ -108,34 +114,37 @@
     std::unique_ptr<VerifiedInputEvent> verifyInputEvent(const InputEvent& event) override;
 
     void setFocusedApplication(
-            int32_t displayId,
+            ui::LogicalDisplayId displayId,
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) override;
-    void setFocusedDisplay(int32_t displayId) override;
+    void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
+    void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) override;
     void setInputDispatchMode(bool enabled, bool frozen) override;
     void setInputFilterEnabled(bool enabled) override;
     bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission,
-                        int32_t displayId) override;
+                        ui::LogicalDisplayId displayId) override;
     void setMaximumObscuringOpacityForTouch(float opacity) override;
 
-    bool transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
-                            bool isDragDrop = false) override;
-    bool transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) override;
+    bool transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
+                              bool isDragDrop = false) override;
+    bool transferTouchOnDisplay(const sp<IBinder>& destChannelToken,
+                                ui::LogicalDisplayId displayId) override;
 
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(
             const std::string& name) override;
     void setFocusedWindow(const android::gui::FocusRequest&) override;
-    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
+    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
                                                                    const std::string& name,
                                                                    gui::Pid pid) override;
     status_t removeInputChannel(const sp<IBinder>& connectionToken) override;
     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;
+    void setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId,
+                                                bool isEligible) override;
 
     std::array<uint8_t, 32> sign(const VerifiedInputEvent& event) const;
 
-    void displayRemoved(int32_t displayId) override;
+    void displayRemoved(ui::LogicalDisplayId displayId) override;
 
     // Public because it's also used by tests to simulate the WindowInfosListener callback
     void onWindowInfosChanged(const gui::WindowInfosUpdate&);
@@ -148,14 +157,15 @@
     void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
                                    std::chrono::nanoseconds delay) override;
 
-    bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
-                           int32_t pointerId) override;
+    bool isPointerInWindow(const sp<IBinder>& token, ui::LogicalDisplayId displayId,
+                           DeviceId deviceId, int32_t pointerId) override;
+
+    void setInputMethodConnectionIsActive(bool isActive) override;
 
 private:
     enum class DropReason {
         NOT_DROPPED,
         POLICY,
-        APP_SWITCH,
         DISABLED,
         BLOCKED,
         STALE,
@@ -172,6 +182,9 @@
     std::condition_variable mDispatcherIsAlive;
     mutable std::condition_variable mDispatcherEnteredIdle;
 
+    // Input event tracer. The tracer will only exist on builds where input tracing is allowed.
+    std::unique_ptr<trace::InputTracerInterface> mTracer GUARDED_BY(mLock);
+
     sp<Looper> mLooper;
 
     std::shared_ptr<const EventEntry> mPendingEvent GUARDED_BY(mLock);
@@ -204,12 +217,17 @@
 
     int64_t mWindowInfosVsyncId GUARDED_BY(mLock);
 
+    std::chrono::milliseconds mMinTimeBetweenUserActivityPokes GUARDED_BY(mLock);
+
+    /** Stores the latest user-activity poke event times per user activity types. */
+    std::array<nsecs_t, USER_ACTIVITY_EVENT_LAST + 1> mLastUserActivityTimes GUARDED_BY(mLock);
+
     // With each iteration, InputDispatcher nominally processes one queued event,
     // a timeout, or a response from an input consumer.
     // This method should only be called on the input dispatcher's own thread.
     void dispatchOnce();
 
-    void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) REQUIRES(mLock);
+    void dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) REQUIRES(mLock);
 
     // Enqueues an inbound event.  Returns true if mLooper->wake() should be called.
     bool enqueueInboundEventLocked(std::unique_ptr<EventEntry> entry) REQUIRES(mLock);
@@ -228,30 +246,22 @@
     // Adds an event to a queue of recent events for debugging purposes.
     void addRecentEventLocked(std::shared_ptr<const EventEntry> entry) REQUIRES(mLock);
 
-    // App switch latency optimization.
-    bool mAppSwitchSawKeyDown GUARDED_BY(mLock);
-    nsecs_t mAppSwitchDueTime GUARDED_BY(mLock);
-
-    bool isAppSwitchKeyEvent(const KeyEntry& keyEntry);
-    bool isAppSwitchPendingLocked() const REQUIRES(mLock);
-    void resetPendingAppSwitchLocked(bool handled) REQUIRES(mLock);
-
     // Blocked event latency optimization.  Drops old events when the user intends
     // to transfer focus to a new application.
     std::shared_ptr<const EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
 
     sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
-            int32_t displayId, float x, float y, bool isStylus = false,
+            ui::LogicalDisplayId displayId, float x, float y, bool isStylus = false,
             bool ignoreDragWindow = false) const REQUIRES(mLock);
     std::vector<InputTarget> findOutsideTargetsLocked(
-            int32_t displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
+            ui::LogicalDisplayId displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
             int32_t pointerId) const REQUIRES(mLock);
 
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
-            int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock);
+            ui::LogicalDisplayId displayId, float x, float y, bool isStylus) const REQUIRES(mLock);
 
-    sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(int32_t displayId) const
-            REQUIRES(mLock);
+    sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(
+            ui::LogicalDisplayId displayId) const REQUIRES(mLock);
 
     std::shared_ptr<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
             REQUIRES(mLock);
@@ -275,7 +285,8 @@
     std::optional<gui::Pid> findMonitorPidByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
     // Input channels that will receive a copy of all input events sent to the provided display.
-    std::unordered_map<int32_t, std::vector<Monitor>> mGlobalMonitorsByDisplay GUARDED_BY(mLock);
+    std::unordered_map<ui::LogicalDisplayId, std::vector<Monitor>> mGlobalMonitorsByDisplay
+            GUARDED_BY(mLock);
 
     const HmacKeyManager mHmacKeyManager;
     const std::array<uint8_t, 32> getSignature(const MotionEntry& motionEntry,
@@ -290,7 +301,9 @@
     void transformMotionEntryForInjectionLocked(MotionEntry&,
                                                 const ui::Transform& injectedTransform) const
             REQUIRES(mLock);
-
+    // Per-display correction of injected events
+    std::map<android::ui::LogicalDisplayId, InputVerifier> mInputFilterVerifiersByDisplay
+            GUARDED_BY(mLock);
     std::condition_variable mInjectionSyncFinished;
     void incrementPendingForegroundDispatches(const EventEntry& entry);
     void decrementPendingForegroundDispatches(const EventEntry& entry);
@@ -334,7 +347,8 @@
     // This map is not really needed, but it helps a lot with debugging (dumpsys input).
     // In the java layer, touch mode states are spread across multiple DisplayContent objects,
     // making harder to snapshot and retrieve them.
-    std::map<int32_t /*displayId*/, bool /*inTouchMode*/> mTouchModePerDisplay GUARDED_BY(mLock);
+    std::map<ui::LogicalDisplayId /*displayId*/, bool /*inTouchMode*/> mTouchModePerDisplay
+            GUARDED_BY(mLock);
 
     class DispatcherWindowListener : public gui::WindowInfosListener {
     public:
@@ -346,27 +360,26 @@
     };
     sp<gui::WindowInfosListener> mWindowInfoListener;
 
-    std::unordered_map<int32_t /*displayId*/, std::vector<sp<android::gui::WindowInfoHandle>>>
+    std::unordered_map<ui::LogicalDisplayId /*displayId*/,
+                       std::vector<sp<android::gui::WindowInfoHandle>>>
             mWindowHandlesByDisplay GUARDED_BY(mLock);
-    std::unordered_map<int32_t /*displayId*/, android::gui::DisplayInfo> mDisplayInfos
+    std::unordered_map<ui::LogicalDisplayId /*displayId*/, android::gui::DisplayInfo> mDisplayInfos
             GUARDED_BY(mLock);
     void setInputWindowsLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
-            int32_t displayId) REQUIRES(mLock);
+            ui::LogicalDisplayId displayId) REQUIRES(mLock);
     // Get a reference to window handles by display, return an empty vector if not found.
     const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesLocked(
-            int32_t displayId) const REQUIRES(mLock);
-    ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock);
+            ui::LogicalDisplayId displayId) const REQUIRES(mLock);
+    ui::Transform getTransformLocked(ui::LogicalDisplayId displayId) const REQUIRES(mLock);
 
     sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
-            const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId = {}) const
-            REQUIRES(mLock);
+            const sp<IBinder>& windowHandleToken,
+            std::optional<ui::LogicalDisplayId> displayId = {}) const REQUIRES(mLock);
     sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
-    std::shared_ptr<InputChannel> getInputChannelLocked(const sp<IBinder>& windowToken) const
-            REQUIRES(mLock);
-    sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(int displayId) const
-            REQUIRES(mLock);
+    sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(
+            ui::LogicalDisplayId displayId) const REQUIRES(mLock);
     bool canWindowReceiveMotionLocked(const sp<android::gui::WindowInfoHandle>& window,
                                       const MotionEntry& motionEntry) const REQUIRES(mLock);
 
@@ -381,20 +394,21 @@
      */
     void updateWindowHandlesForDisplayLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
-            int32_t displayId) REQUIRES(mLock);
+            ui::LogicalDisplayId displayId) REQUIRES(mLock);
 
-    std::unordered_map<int32_t, TouchState> mTouchStatesByDisplay GUARDED_BY(mLock);
+    std::unordered_map<ui::LogicalDisplayId /*displayId*/, TouchState> mTouchStatesByDisplay
+            GUARDED_BY(mLock);
     std::unique_ptr<DragState> mDragState GUARDED_BY(mLock);
 
     void setFocusedApplicationLocked(
-            int32_t displayId,
+            ui::LogicalDisplayId displayId,
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) REQUIRES(mLock);
     // Focused applications.
-    std::unordered_map<int32_t, std::shared_ptr<InputApplicationHandle>>
+    std::unordered_map<ui::LogicalDisplayId /*displayId*/, std::shared_ptr<InputApplicationHandle>>
             mFocusedApplicationHandlesByDisplay GUARDED_BY(mLock);
 
     // Top focused display.
-    int32_t mFocusedDisplayId GUARDED_BY(mLock);
+    ui::LogicalDisplayId mFocusedDisplayId GUARDED_BY(mLock);
 
     // Keeps track of the focused window per display and determines focus changes.
     FocusResolver mFocusResolver GUARDED_BY(mLock);
@@ -411,13 +425,15 @@
 
     // 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);
+    std::vector<ui::LogicalDisplayId /*displayId*/> mIneligibleDisplaysForPointerCapture
+            GUARDED_BY(mLock);
 
     // Disable Pointer Capture as a result of loss of window focus.
     void disablePointerCaptureForcedLocked() REQUIRES(mLock);
 
     // Set the Pointer Capture state in the Policy.
-    void setPointerCaptureLocked(bool enable) REQUIRES(mLock);
+    // The window is not nullptr for requests to enable, otherwise it is nullptr.
+    void setPointerCaptureLocked(const sp<IBinder>& window) REQUIRES(mLock);
 
     // Dispatcher state at time of last ANR.
     std::string mLastAnrState GUARDED_BY(mLock);
@@ -435,9 +451,9 @@
     bool dispatchDeviceResetLocked(nsecs_t currentTime, const DeviceResetEntry& entry)
             REQUIRES(mLock);
     bool dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
-                           DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
+                           DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
     bool dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<const MotionEntry> entry,
-                              DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
+                              DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
     void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<const FocusEntry> entry)
             REQUIRES(mLock);
     void dispatchPointerCaptureChangedLocked(
@@ -449,7 +465,7 @@
     void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<const EventEntry> entry,
                              const std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
     void dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<const SensorEntry>& entry,
-                              DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
+                              DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
     void dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<const DragEntry> entry)
             REQUIRES(mLock);
     void logOutboundKeyDetails(const char* prefix, const KeyEntry& entry);
@@ -466,7 +482,7 @@
 
     bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry);
 
-    bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) REQUIRES(mLock);
+    bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) const REQUIRES(mLock);
 
     /**
      * Time to stop waiting for the events to be processed while trying to dispatch a key.
@@ -485,7 +501,7 @@
     /**
      * The displayId that the focused application is associated with.
      */
-    int32_t mAwaitedApplicationDisplayId GUARDED_BY(mLock);
+    ui::LogicalDisplayId mAwaitedApplicationDisplayId GUARDED_BY(mLock);
     void processNoFocusedWindowAnrLocked() REQUIRES(mLock);
 
     /**
@@ -519,9 +535,9 @@
     // shade is pulled down while we are counting down the timeout).
     void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
 
-    int32_t getTargetDisplayId(const EventEntry& entry);
+    ui::LogicalDisplayId getTargetDisplayId(const EventEntry& entry);
     sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
-            nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+            nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<InputTarget> findTouchedWindowTargetsLocked(
             nsecs_t currentTime, const MotionEntry& entry,
@@ -544,13 +560,13 @@
                                       std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                       std::optional<nsecs_t> firstDownTimeInTarget,
                                       std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
-    void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
-            REQUIRES(mLock);
+    void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
+                                          ui::LogicalDisplayId displayId) REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
     // Enqueue a drag event if needed, and update the touch state.
     // Uses findTouchedWindowTargetsLocked to make the decision
     void addDragEventLocked(const MotionEntry& entry) REQUIRES(mLock);
-    void finishDragAndDrop(int32_t displayId, float x, float y) REQUIRES(mLock);
+    void finishDragAndDrop(ui::LogicalDisplayId displayId, float x, float y) REQUIRES(mLock);
 
     struct TouchOcclusionInfo {
         bool hasBlockingOcclusion;
@@ -561,11 +577,11 @@
     };
 
     TouchOcclusionInfo computeTouchOcclusionInfoLocked(
-            const sp<android::gui::WindowInfoHandle>& windowHandle, int32_t x, int32_t y) const
+            const sp<android::gui::WindowInfoHandle>& windowHandle, float x, float y) const
             REQUIRES(mLock);
     bool isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const REQUIRES(mLock);
     bool isWindowObscuredAtPointLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                       int32_t x, int32_t y) const REQUIRES(mLock);
+                                       float x, float y) const REQUIRES(mLock);
     bool isWindowObscuredLocked(const sp<android::gui::WindowInfoHandle>& windowHandle) const
             REQUIRES(mLock);
     std::string dumpWindowForTouchOcclusion(const android::gui::WindowInfo* info,
@@ -611,20 +627,21 @@
             REQUIRES(mLock);
     void synthesizeCancelationEventsForMonitorsLocked(const CancelationOptions& options)
             REQUIRES(mLock);
-    void synthesizeCancelationEventsForInputChannelLocked(
-            const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options)
+    void synthesizeCancelationEventsForWindowLocked(const sp<gui::WindowInfoHandle>&,
+                                                    const CancelationOptions&,
+                                                    const std::shared_ptr<Connection>& = nullptr)
             REQUIRES(mLock);
+    // This is a convenience function used to generate cancellation for a connection without having
+    // to check whether it's a monitor or a window. For non-monitors, the window handle must not be
+    // null. Always prefer the "-ForWindow" method above when explicitly dealing with windows.
     void synthesizeCancelationEventsForConnectionLocked(
-            const std::shared_ptr<Connection>& connection, const CancelationOptions& options)
-            REQUIRES(mLock);
+            const std::shared_ptr<Connection>& connection, const CancelationOptions& options,
+            const sp<gui::WindowInfoHandle>& window) REQUIRES(mLock);
 
     void synthesizePointerDownEventsForConnectionLocked(
             const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
-            ftl::Flags<InputTarget::Flags> targetFlags) REQUIRES(mLock);
-
-    void synthesizeCancelationEventsForWindowLocked(
-            const sp<android::gui::WindowInfoHandle>& windowHandle,
-            const CancelationOptions& options) REQUIRES(mLock);
+            ftl::Flags<InputTarget::Flags> targetFlags,
+            const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) REQUIRES(mLock);
 
     // Splitting motion events across windows. When splitting motion event for a target,
     // splitDownTime refers to the time of first 'down' event on that particular target
@@ -652,7 +669,10 @@
                                         bool handled, nsecs_t consumeTime) REQUIRES(mLock);
     void doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
                                                 const KeyEntry& entry) REQUIRES(mLock);
-    void onFocusChangedLocked(const FocusResolver::FocusChanges& changes) REQUIRES(mLock);
+    void onFocusChangedLocked(const FocusResolver::FocusChanges& changes,
+                              const std::unique_ptr<trace::EventTrackerInterface>& traceTracker,
+                              const sp<gui::WindowInfoHandle> removedFocusedWindowHandle = nullptr)
+            REQUIRES(mLock);
     void sendFocusChangedCommandLocked(const sp<IBinder>& oldToken, const sp<IBinder>& newToken)
             REQUIRES(mLock);
     void sendDropWindowCommandLocked(const sp<IBinder>& token, float x, float y) REQUIRES(mLock);
@@ -664,14 +684,14 @@
                                   const std::string& reason) REQUIRES(mLock);
     void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
             REQUIRES(mLock);
-    std::map<int32_t /*displayId*/, InputVerifier> mVerifiersByDisplay;
+    std::map<ui::LogicalDisplayId /*displayId*/, InputVerifier> mVerifiersByDisplay;
     // Returns a fallback KeyEntry that should be sent to the connection, if required.
     std::unique_ptr<const KeyEntry> afterKeyEventLockedInterruptable(
             const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
             const KeyEntry& keyEntry, bool handled) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
-    std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+    std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId /*displayId*/>
     findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
     // Statistics gathering.
@@ -690,14 +710,17 @@
     void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                             const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
                             const sp<android::gui::WindowInfoHandle>& newWindowHandle,
-                            TouchState& state, int32_t deviceId, int32_t pointerId,
+                            TouchState& state, DeviceId deviceId,
+                            const PointerProperties& pointerProperties,
                             std::vector<InputTarget>& targets) const REQUIRES(mLock);
     void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
                                 ftl::Flags<InputTarget::Flags> newTargetFlags,
                                 const sp<android::gui::WindowInfoHandle> fromWindowHandle,
                                 const sp<android::gui::WindowInfoHandle> toWindowHandle,
-                                TouchState& state, int32_t deviceId,
-                                std::bitset<MAX_POINTER_ID + 1> pointerIds) REQUIRES(mLock);
+                                TouchState& state, DeviceId deviceId,
+                                const std::vector<PointerProperties>& pointers,
+                                const std::unique_ptr<trace::EventTrackerInterface>& traceTracker)
+            REQUIRES(mLock);
 
     sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index a4ac4fb..46e7e8b 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -28,7 +28,8 @@
 
 InputState::~InputState() {}
 
-bool InputState::isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const {
+bool InputState::isHovering(DeviceId deviceId, uint32_t source,
+                            ui::LogicalDisplayId displayId) const {
     for (const MotionMemento& memento : mMotionMementos) {
         if (memento.deviceId == deviceId && memento.source == source &&
             memento.displayId == displayId && memento.hovering) {
@@ -95,12 +96,14 @@
         return true;
     }
 
-    if (!mMotionMementos.empty()) {
-        const MotionMemento& lastMemento = mMotionMementos.back();
-        if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
-            !isStylusEvent(entry.source, entry.pointerProperties)) {
-            // We already have a stylus stream, and the new event is not from stylus.
-            return false;
+    if (!input_flags::enable_multi_device_same_window_stream()) {
+        if (!mMotionMementos.empty()) {
+            const MotionMemento& lastMemento = mMotionMementos.back();
+            if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
+                !isStylusEvent(entry.source, entry.pointerProperties)) {
+                // We already have a stylus stream, and the new event is not from stylus.
+                return false;
+            }
         }
     }
 
@@ -195,6 +198,16 @@
     }
 }
 
+std::optional<std::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>>
+InputState::getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const {
+    ssize_t index = findMotionMemento(entry, hovering);
+    if (index == -1) {
+        return std::nullopt;
+    }
+    return std::make_pair(mMotionMementos[index].pointerProperties,
+                          mMotionMementos[index].pointerCoords);
+}
+
 ssize_t InputState::findKeyMemento(const KeyEntry& entry) const {
     for (size_t i = 0; i < mKeyMementos.size(); i++) {
         const KeyMemento& memento = mKeyMementos[i];
@@ -311,16 +324,6 @@
             return true;
         }
 
-        // Use the previous stream cancellation logic to generate all HOVER_EXIT events.
-        // If this hover event was generated as a result of the pointer leaving the window,
-        // the HOVER_EXIT event should have the same coordinates as the previous
-        // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same
-        // coordinates as the previous event by cancelling the stream here. With this approach, the
-        // HOVER_EXIT event is generated from the previous event.
-        if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) {
-            return true;
-        }
-
         // If the stream changes its source, just cancel the current gesture to be safe. It's
         // possible that the app isn't handling source changes properly
         if (motionEntry.source != lastMemento.source) {
@@ -336,33 +339,34 @@
         // it's unlikely that those two streams would be consistent with each other. Therefore,
         // cancel the previous gesture if the display id changes.
         if (motionEntry.displayId != lastMemento.displayId) {
-            LOG(INFO) << "Canceling stream: last displayId was "
-                      << inputEventSourceToString(lastMemento.displayId) << " and new event is "
-                      << motionEntry;
+            LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
+                      << " and new event is " << motionEntry;
             return true;
         }
 
         return false;
     }
 
-    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
-        // A stylus is already active.
-        if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
-            actionMasked == AMOTION_EVENT_ACTION_DOWN) {
-            // If this new event is from a different device, then cancel the old
-            // stylus and allow the new stylus to take over, but only if it's going down.
-            // Otherwise, they will start to race each other.
-            return true;
+    if (!input_flags::enable_multi_device_same_window_stream()) {
+        if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
+            // A stylus is already active.
+            if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
+                actionMasked == AMOTION_EVENT_ACTION_DOWN) {
+                // If this new event is from a different device, then cancel the old
+                // stylus and allow the new stylus to take over, but only if it's going down.
+                // Otherwise, they will start to race each other.
+                return true;
+            }
+
+            // Keep the current stylus gesture.
+            return false;
         }
 
-        // Keep the current stylus gesture.
-        return false;
-    }
-
-    // Cancel the current gesture if this is a start of a new gesture from a new device.
-    if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
-        actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
-        return true;
+        // Cancel the current gesture if this is a start of a new gesture from a new device.
+        if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
+            actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
+            return true;
+        }
     }
     // By default, don't cancel any events.
     return false;
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index b0e4209..2808ba7 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -36,7 +36,7 @@
 
     // Returns true if the specified source is known to have received a hover enter
     // motion event.
-    bool isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const;
+    bool isHovering(DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId) const;
 
     // Records tracking information for a key event that has just been published.
     // Returns true if the event should be delivered, false if it is inconsistent
@@ -48,6 +48,15 @@
     // and should be skipped.
     bool trackMotion(const MotionEntry& entry, int32_t flags);
 
+    /**
+     * Return the PointerProperties and the PointerCoords for the last event, if found. Return
+     * std::nullopt if not found. We should not return std::vector<PointerCoords> in isolation,
+     * because the pointers can technically be stored in the vector in any order, so the
+     * PointerProperties are needed to specify the order in which the pointer coords are stored.
+     */
+    std::optional<std::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>>
+    getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const;
+
     // Create cancel events for the previous stream if the current motionEntry requires it.
     std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry);
 
@@ -81,7 +90,7 @@
     struct KeyMemento {
         DeviceId deviceId;
         uint32_t source;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
         int32_t keyCode;
         int32_t scanCode;
         int32_t metaState;
@@ -93,7 +102,7 @@
     struct MotionMemento {
         DeviceId deviceId;
         uint32_t source;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
         int32_t flags;
         float xPrecision;
         float yPrecision;
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index c02c5d6..f9a2855 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -22,45 +22,72 @@
 #include <inttypes.h>
 #include <string>
 
+using android::base::Error;
+using android::base::Result;
 using android::base::StringPrintf;
 
 namespace android::inputdispatcher {
 
-void InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds,
-                              const ui::Transform& transform) {
+namespace {
+
+const static ui::Transform kIdentityTransform{};
+
+}
+
+InputTarget::InputTarget(const std::shared_ptr<Connection>& connection, ftl::Flags<Flags> flags)
+      : connection(connection), flags(flags) {}
+
+Result<void> InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds,
+                                      const ui::Transform& transform) {
     // The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no
     // valid pointer property from the input event.
     if (newPointerIds.none()) {
         setDefaultPointerTransform(transform);
-        return;
+        return {};
     }
 
     // Ensure that the new set of pointers doesn't overlap with the current set of pointers.
-    if ((pointerIds & newPointerIds).any()) {
-        LOG(FATAL) << __func__ << " - overlap with incoming pointers "
-                   << bitsetToString(newPointerIds) << " in " << *this;
+    if ((getPointerIds() & newPointerIds).any()) {
+        return Error() << __func__ << " - overlap with incoming pointers "
+                       << bitsetToString(newPointerIds) << " in " << *this;
     }
 
-    pointerIds |= newPointerIds;
-    for (size_t i = 0; i < newPointerIds.size(); i++) {
-        if (!newPointerIds.test(i)) {
-            continue;
+    for (auto& [existingTransform, existingPointers] : mPointerTransforms) {
+        if (transform == existingTransform) {
+            existingPointers |= newPointerIds;
+            return {};
         }
-        pointerTransforms[i] = transform;
     }
+    mPointerTransforms.emplace_back(transform, newPointerIds);
+    return {};
 }
 
 void InputTarget::setDefaultPointerTransform(const ui::Transform& transform) {
-    pointerIds.reset();
-    pointerTransforms[0] = transform;
+    mPointerTransforms = {{transform, {}}};
 }
 
 bool InputTarget::useDefaultPointerTransform() const {
-    return pointerIds.none();
+    return mPointerTransforms.size() <= 1;
 }
 
 const ui::Transform& InputTarget::getDefaultPointerTransform() const {
-    return pointerTransforms[0];
+    if (!useDefaultPointerTransform()) {
+        LOG(FATAL) << __func__ << ": Not using default pointer transform";
+    }
+    return mPointerTransforms.size() == 1 ? mPointerTransforms[0].first : kIdentityTransform;
+}
+
+const ui::Transform& InputTarget::getTransformForPointer(int32_t pointerId) const {
+    for (const auto& [transform, ids] : mPointerTransforms) {
+        if (ids.test(pointerId)) {
+            return transform;
+        }
+    }
+
+    LOG(FATAL) << __func__
+               << ": Cannot get transform: The following Pointer ID does not exist in target: "
+               << pointerId;
+    return kIdentityTransform;
 }
 
 std::string InputTarget::getPointerInfoString() const {
@@ -71,21 +98,25 @@
         return out;
     }
 
-    for (uint32_t i = 0; i < pointerIds.size(); i++) {
-        if (!pointerIds.test(i)) {
-            continue;
-        }
-
-        const std::string name = "pointerId " + std::to_string(i) + ":";
-        pointerTransforms[i].dump(out, name.c_str(), "        ");
+    for (const auto& [transform, ids] : mPointerTransforms) {
+        const std::string name = "pointerIds " + bitsetToString(ids) + ":";
+        transform.dump(out, name.c_str(), "        ");
     }
     return out;
 }
 
+std::bitset<MAX_POINTER_ID + 1> InputTarget::getPointerIds() const {
+    PointerIds allIds;
+    for (const auto& [_, ids] : mPointerTransforms) {
+        allIds |= ids;
+    }
+    return allIds;
+}
+
 std::ostream& operator<<(std::ostream& out, const InputTarget& target) {
-    out << "{inputChannel=";
-    if (target.inputChannel != nullptr) {
-        out << target.inputChannel->getName();
+    out << "{connection=";
+    if (target.connection != nullptr) {
+        out << target.connection->getInputChannelName();
     } else {
         out << "<null>";
     }
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index aef866b..90374f1 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -18,11 +18,11 @@
 
 #include <ftl/flags.h>
 #include <gui/WindowInfo.h>
-#include <gui/constants.h>
-#include <input/InputTransport.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
 #include <bitset>
+#include "Connection.h"
+#include "InputTargetFlags.h"
 
 namespace android::inputdispatcher {
 
@@ -32,30 +32,9 @@
  * be added to input event coordinates to compensate for the absolute position of the
  * window area.
  */
-struct InputTarget {
-    enum class Flags : uint32_t {
-        /* This flag indicates that the event is being delivered to a foreground application. */
-        FOREGROUND = 1 << 0,
-
-        /* This flag indicates that the MotionEvent falls within the area of the target
-         * obscured by another visible window above it.  The motion event should be
-         * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
-        WINDOW_IS_OBSCURED = 1 << 1,
-
-        /* This flag indicates that a motion event is being split across multiple windows. */
-        SPLIT = 1 << 2,
-
-        /* This flag indicates that the pointer coordinates dispatched to the application
-         * will be zeroed out to avoid revealing information to an application. This is
-         * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
-         * the same UID from watching all touches. */
-        ZERO_COORDS = 1 << 3,
-
-        /* This flag indicates that the target of a MotionEvent is partly or wholly
-         * obscured by another visible window above it.  The motion event should be
-         * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */
-        WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14,
-    };
+class InputTarget {
+public:
+    using Flags = InputTargetFlags;
 
     enum class DispatchMode {
         /* This flag indicates that the event should be sent as is.
@@ -85,8 +64,8 @@
         ftl_last = SLIPPERY_ENTER,
     };
 
-    // The input channel to be targeted.
-    std::shared_ptr<InputChannel> inputChannel;
+    // The input connection to be targeted.
+    std::shared_ptr<Connection> connection;
 
     // Flags for the input target.
     ftl::Flags<Flags> flags;
@@ -101,21 +80,19 @@
     // Current display transform. Used for compatibility for raw coordinates.
     ui::Transform displayTransform;
 
-    // The subset of pointer ids to include in motion events dispatched to this input target
-    // if FLAG_SPLIT is set.
-    std::bitset<MAX_POINTER_ID + 1> pointerIds;
     // Event time for the first motion event (ACTION_DOWN) dispatched to this input target if
     // FLAG_SPLIT is set.
     std::optional<nsecs_t> firstDownTimeInTarget;
-    // The data is stored by the pointerId. Use the bit position of pointerIds to look up
-    // Transform per pointerId.
-    ui::Transform pointerTransforms[MAX_POINTERS];
 
     // The window that this input target is being dispatched to. It is possible for this to be
     // null for cases like global monitors.
     sp<gui::WindowInfoHandle> windowHandle;
 
-    void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform);
+    InputTarget() = default;
+    InputTarget(const std::shared_ptr<Connection>&, ftl::Flags<Flags> = {});
+
+    android::base::Result<void> addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                            const ui::Transform& transform);
     void setDefaultPointerTransform(const ui::Transform& transform);
 
     /**
@@ -132,7 +109,22 @@
      */
     const ui::Transform& getDefaultPointerTransform() const;
 
+    const ui::Transform& getTransformForPointer(int32_t pointerId) const;
+
+    std::bitset<MAX_POINTER_ID + 1> getPointerIds() const;
+
     std::string getPointerInfoString() const;
+
+private:
+    template <typename K, typename V>
+    using ArrayMap = std::vector<std::pair<K, V>>;
+    using PointerIds = std::bitset<MAX_POINTER_ID + 1>;
+    // The mapping of pointer IDs to the transform that should be used for that collection of IDs.
+    // Each of the pointer IDs are mutually disjoint, and their union makes up pointer IDs to
+    // include in the motion events dispatched to this target. We use an ArrayMap to store this to
+    // avoid having to define hash or comparison functions for ui::Transform, which would be needed
+    // to use std::unordered_map or std::map respectively.
+    ArrayMap<ui::Transform, PointerIds> mPointerTransforms;
 };
 
 std::ostream& operator<<(std::ostream& out, const InputTarget& target);
diff --git a/services/inputflinger/dispatcher/InputTargetFlags.h b/services/inputflinger/dispatcher/InputTargetFlags.h
new file mode 100644
index 0000000..efebb18
--- /dev/null
+++ b/services/inputflinger/dispatcher/InputTargetFlags.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/flags.h>
+
+namespace android::inputdispatcher {
+
+enum class InputTargetFlags : uint32_t {
+    /* This flag indicates that the event is being delivered to a foreground application. */
+    FOREGROUND = 1 << 0,
+
+    /* This flag indicates that the MotionEvent falls within the area of the target
+     * obscured by another visible window above it.  The motion event should be
+     * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+    WINDOW_IS_OBSCURED = 1 << 1,
+
+    /* This flag indicates that a motion event is being split across multiple windows. */
+    SPLIT = 1 << 2,
+
+    /* This flag indicates that the pointer coordinates dispatched to the application
+     * will be zeroed out to avoid revealing information to an application. This is
+     * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
+     * the same UID from watching all touches. */
+    ZERO_COORDS = 1 << 3,
+
+    /* This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an ACTION_DOWN. This is typically used to allow gestures to be
+     * directed to an unfocused window without bringing it into focus. The motion event should be
+     * delivered with flag AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE. */
+    NO_FOCUS_CHANGE = 1 << 4,
+
+    /* This flag indicates that the target of a MotionEvent is partly or wholly
+     * obscured by another visible window above it.  The motion event should be
+     * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */
+    WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14,
+};
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Monitor.cpp b/services/inputflinger/dispatcher/Monitor.cpp
index 204791e..4e77d90 100644
--- a/services/inputflinger/dispatcher/Monitor.cpp
+++ b/services/inputflinger/dispatcher/Monitor.cpp
@@ -19,7 +19,7 @@
 namespace android::inputdispatcher {
 
 // --- Monitor ---
-Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel, gui::Pid pid)
-      : inputChannel(inputChannel), pid(pid) {}
+Monitor::Monitor(const std::shared_ptr<Connection>& connection, gui::Pid pid)
+      : connection(connection), pid(pid) {}
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Monitor.h b/services/inputflinger/dispatcher/Monitor.h
index 1b1eb3a..d15a222 100644
--- a/services/inputflinger/dispatcher/Monitor.h
+++ b/services/inputflinger/dispatcher/Monitor.h
@@ -17,16 +17,16 @@
 #pragma once
 
 #include <gui/PidUid.h>
-#include <input/InputTransport.h>
+#include "Connection.h"
 
 namespace android::inputdispatcher {
 
 struct Monitor {
-    std::shared_ptr<InputChannel> inputChannel; // never null
+    std::shared_ptr<Connection> connection; // never null
 
     gui::Pid pid;
 
-    explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel, gui::Pid pid);
+    explicit Monitor(const std::shared_ptr<Connection>& connection, gui::Pid pid);
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index e8d8c18..0c9ad3c 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -70,14 +70,14 @@
     });
 }
 
-void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
-                                   InputTarget::DispatchMode dispatchMode,
-                                   ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                                   std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
-                                   std::optional<nsecs_t> firstDownTimeInTarget) {
-    if (touchingPointerIds.none()) {
+android::base::Result<void> TouchState::addOrUpdateWindow(
+        const sp<WindowInfoHandle>& windowHandle, InputTarget::DispatchMode dispatchMode,
+        ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
+        const std::vector<PointerProperties>& touchingPointers,
+        std::optional<nsecs_t> firstDownTimeInTarget) {
+    if (touchingPointers.empty()) {
         LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
-        return;
+        return android::base::Error();
     }
     for (TouchedWindow& touchedWindow : windows) {
         // We do not compare windows by token here because two windows that share the same token
@@ -91,36 +91,38 @@
             // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have
             // downTime set initially. Need to update existing window when a pointer is down for the
             // window.
-            touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
+            android::base::Result<void> addResult =
+                    touchedWindow.addTouchingPointers(deviceId, touchingPointers);
             if (firstDownTimeInTarget) {
                 touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
             }
-            return;
+            return addResult;
         }
     }
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
     touchedWindow.dispatchMode = dispatchMode;
     touchedWindow.targetFlags = targetFlags;
-    touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
+    touchedWindow.addTouchingPointers(deviceId, touchingPointers);
     if (firstDownTimeInTarget) {
         touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
     }
     windows.push_back(touchedWindow);
+    return {};
 }
 
 void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
-                                            DeviceId deviceId, int32_t hoveringPointerId) {
+                                            DeviceId deviceId, const PointerProperties& pointer) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
+            touchedWindow.addHoveringPointer(deviceId, pointer);
             return;
         }
     }
 
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
-    touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
+    touchedWindow.addHoveringPointer(deviceId, pointer);
     windows.push_back(touchedWindow);
 }
 
@@ -178,9 +180,11 @@
     clearWindowsWithoutPointers();
 }
 
-sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
-    for (size_t i = 0; i < windows.size(); i++) {
-        const TouchedWindow& window = windows[i];
+sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle(DeviceId deviceId) const {
+    for (const auto& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             return window.windowHandle;
         }
@@ -188,10 +192,13 @@
     return nullptr;
 }
 
-bool TouchState::isSlippery() const {
+bool TouchState::isSlippery(DeviceId deviceId) const {
     // Must have exactly one foreground window.
     bool haveSlipperyForegroundWindow = false;
     for (const TouchedWindow& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             if (haveSlipperyForegroundWindow ||
                 !window.windowHandle->getInfo()->inputConfig.test(
@@ -204,9 +211,11 @@
     return haveSlipperyForegroundWindow;
 }
 
-sp<WindowInfoHandle> TouchState::getWallpaperWindow() const {
-    for (size_t i = 0; i < windows.size(); i++) {
-        const TouchedWindow& window = windows[i];
+sp<WindowInfoHandle> TouchState::getWallpaperWindow(DeviceId deviceId) const {
+    for (const auto& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.windowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
             return window.windowHandle;
@@ -234,6 +243,11 @@
     });
 }
 
+bool TouchState::hasActiveStylus() const {
+    return std::any_of(windows.begin(), windows.end(),
+                       [](const TouchedWindow& window) { return window.hasActiveStylus(); });
+}
+
 std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId,
                                                                          int32_t pointerId) const {
     std::set<sp<WindowInfoHandle>> out;
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index e0a84e8..9d4bb3d 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -43,14 +43,14 @@
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId,
                                          const sp<android::gui::WindowInfoHandle>& windowHandle);
-    void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                           InputTarget::DispatchMode dispatchMode,
-                           ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                           std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
-                           std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
+    android::base::Result<void> addOrUpdateWindow(
+            const sp<android::gui::WindowInfoHandle>& windowHandle,
+            InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
+            DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers,
+            std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                    DeviceId deviceId, int32_t hoveringPointerId);
-    void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId);
+                                    DeviceId deviceId, const PointerProperties& pointer);
+    void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
     void clearHoveringPointers(DeviceId deviceId);
 
     void removeAllPointersForDevice(DeviceId deviceId);
@@ -64,15 +64,17 @@
     // set to false.
     void cancelPointersForNonPilferingWindows();
 
-    sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
-    bool isSlippery() const;
-    sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
+    sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle(DeviceId deviceId) const;
+    bool isSlippery(DeviceId deviceId) const;
+    sp<android::gui::WindowInfoHandle> getWallpaperWindow(DeviceId deviceId) const;
     const TouchedWindow& getTouchedWindow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const;
     // Whether any of the windows are currently being touched
     bool isDown(DeviceId deviceId) const;
     bool hasHoveringPointers(DeviceId deviceId) const;
 
+    bool hasActiveStylus() const;
+
     std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
             DeviceId deviceId, int32_t pointerId) const;
     std::string dump() const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index cd0500c..1f86f66 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -20,15 +20,27 @@
 #include <android-base/stringprintf.h>
 #include <input/PrintTools.h>
 
+using android::base::Result;
 using android::base::StringPrintf;
 
 namespace android {
 
 namespace inputdispatcher {
 
+namespace {
+
+bool hasPointerId(const std::vector<PointerProperties>& pointers, int32_t pointerId) {
+    return std::find_if(pointers.begin(), pointers.end(),
+                        [&pointerId](const PointerProperties& properties) {
+                            return properties.id == pointerId;
+                        }) != pointers.end();
+}
+
+} // namespace
+
 bool TouchedWindow::hasHoveringPointers() const {
     for (const auto& [_, state] : mDeviceStates) {
-        if (state.hoveringPointerIds.any()) {
+        if (!state.hoveringPointers.empty()) {
             return true;
         }
     }
@@ -42,7 +54,7 @@
     }
     const DeviceState& state = stateIt->second;
 
-    return state.hoveringPointerIds.any();
+    return !state.hoveringPointers.empty();
 }
 
 void TouchedWindow::clearHoveringPointers(DeviceId deviceId) {
@@ -51,7 +63,7 @@
         return;
     }
     DeviceState& state = stateIt->second;
-    state.hoveringPointerIds.reset();
+    state.hoveringPointers.clear();
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
     }
@@ -63,22 +75,43 @@
         return false;
     }
     const DeviceState& state = stateIt->second;
-
-    return state.hoveringPointerIds.test(pointerId);
+    return hasPointerId(state.hoveringPointers, pointerId);
 }
 
-void TouchedWindow::addHoveringPointer(DeviceId deviceId, int32_t pointerId) {
-    mDeviceStates[deviceId].hoveringPointerIds.set(pointerId);
+void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) {
+    std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
+    const size_t initialSize = hoveringPointers.size();
+    std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) {
+        return properties.id == pointer.id;
+    });
+    if (hoveringPointers.size() != initialSize) {
+        LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this;
+    }
+    hoveringPointers.push_back(pointer);
 }
 
-void TouchedWindow::addTouchingPointers(DeviceId deviceId,
-                                        std::bitset<MAX_POINTER_ID + 1> pointers) {
-    mDeviceStates[deviceId].touchingPointerIds |= pointers;
+Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId,
+                                                const std::vector<PointerProperties>& pointers) {
+    std::vector<PointerProperties>& touchingPointers = mDeviceStates[deviceId].touchingPointers;
+    const size_t initialSize = touchingPointers.size();
+    for (const PointerProperties& pointer : pointers) {
+        std::erase_if(touchingPointers, [&pointer](const PointerProperties& properties) {
+            return properties.id == pointer.id;
+        });
+    }
+    const bool foundInconsistentState = touchingPointers.size() != initialSize;
+    touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end());
+    if (foundInconsistentState) {
+        LOG(ERROR) << __func__ << ": " << dumpVector(pointers, streamableToString) << ", device "
+                   << deviceId << " already in " << *this;
+        return android::base::Error();
+    }
+    return {};
 }
 
 bool TouchedWindow::hasTouchingPointers() const {
     for (const auto& [_, state] : mDeviceStates) {
-        if (state.touchingPointerIds.any()) {
+        if (!state.touchingPointers.empty()) {
             return true;
         }
     }
@@ -86,21 +119,25 @@
 }
 
 bool TouchedWindow::hasTouchingPointers(DeviceId deviceId) const {
-    return getTouchingPointers(deviceId).any();
+    return !getTouchingPointers(deviceId).empty();
 }
 
 bool TouchedWindow::hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const {
-    return getTouchingPointers(deviceId).test(pointerId);
+    const auto stateIt = mDeviceStates.find(deviceId);
+    if (stateIt == mDeviceStates.end()) {
+        return false;
+    }
+    const DeviceState& state = stateIt->second;
+    return hasPointerId(state.touchingPointers, pointerId);
 }
 
-std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getTouchingPointers(DeviceId deviceId) const {
+std::vector<PointerProperties> TouchedWindow::getTouchingPointers(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return {};
     }
     const DeviceState& state = stateIt->second;
-
-    return state.touchingPointerIds;
+    return state.touchingPointers;
 }
 
 void TouchedWindow::removeTouchingPointer(DeviceId deviceId, int32_t pointerId) {
@@ -118,7 +155,10 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.touchingPointerIds &= ~pointers;
+    std::erase_if(state.touchingPointers, [&pointers](const PointerProperties& properties) {
+        return pointers.test(properties.id);
+    });
+
     state.pilferingPointerIds &= ~pointers;
 
     if (!state.hasPointers()) {
@@ -126,10 +166,26 @@
     }
 }
 
+bool TouchedWindow::hasActiveStylus() const {
+    for (const auto& [_, state] : mDeviceStates) {
+        for (const PointerProperties& properties : state.touchingPointers) {
+            if (properties.toolType == ToolType::STYLUS) {
+                return true;
+            }
+        }
+        for (const PointerProperties& properties : state.hoveringPointers) {
+            if (properties.toolType == ToolType::STYLUS) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 std::set<DeviceId> TouchedWindow::getTouchingDeviceIds() const {
     std::set<DeviceId> deviceIds;
     for (const auto& [deviceId, deviceState] : mDeviceStates) {
-        if (deviceState.touchingPointerIds.any()) {
+        if (!deviceState.touchingPointers.empty()) {
             deviceIds.insert(deviceId);
         }
     }
@@ -198,7 +254,7 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.touchingPointerIds.reset();
+    state.touchingPointers.clear();
     state.pilferingPointerIds.reset();
     state.downTimeInTarget.reset();
 
@@ -214,7 +270,9 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.hoveringPointerIds.set(pointerId, false);
+    std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) {
+        return properties.id == pointerId;
+    });
 
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
@@ -228,7 +286,7 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.hoveringPointerIds.reset();
+    state.hoveringPointers.clear();
 
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
@@ -236,11 +294,11 @@
 }
 
 std::string TouchedWindow::deviceStateToString(const TouchedWindow::DeviceState& state) {
-    return StringPrintf("[touchingPointerIds=%s, "
-                        "downTimeInTarget=%s, hoveringPointerIds=%s, pilferingPointerIds=%s]",
-                        bitsetToString(state.touchingPointerIds).c_str(),
+    return StringPrintf("[touchingPointers=%s, "
+                        "downTimeInTarget=%s, hoveringPointers=%s, pilferingPointerIds=%s]",
+                        dumpVector(state.touchingPointers, streamableToString).c_str(),
                         toString(state.downTimeInTarget).c_str(),
-                        bitsetToString(state.hoveringPointerIds).c_str(),
+                        dumpVector(state.hoveringPointers, streamableToString).c_str(),
                         bitsetToString(state.pilferingPointerIds).c_str());
 }
 
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index c604353..4f0ad16 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -38,17 +38,19 @@
     bool hasHoveringPointers() const;
     bool hasHoveringPointers(DeviceId deviceId) const;
     bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
-    void addHoveringPointer(DeviceId deviceId, int32_t pointerId);
+    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
 
     // Touching
     bool hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const;
     bool hasTouchingPointers() const;
     bool hasTouchingPointers(DeviceId deviceId) const;
-    std::bitset<MAX_POINTER_ID + 1> getTouchingPointers(DeviceId deviceId) const;
-    void addTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+    std::vector<PointerProperties> getTouchingPointers(DeviceId deviceId) const;
+    android::base::Result<void> addTouchingPointers(DeviceId deviceId,
+                                                    const std::vector<PointerProperties>& pointers);
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+    bool hasActiveStylus() const;
     std::set<DeviceId> getTouchingDeviceIds() const;
 
     // Pilfering pointers
@@ -69,16 +71,16 @@
 
 private:
     struct DeviceState {
-        std::bitset<MAX_POINTER_ID + 1> touchingPointerIds;
+        std::vector<PointerProperties> touchingPointers;
         // The pointer ids of the pointers that this window is currently pilfering, by device
         std::bitset<MAX_POINTER_ID + 1> pilferingPointerIds;
         // Time at which the first action down occurred on this window, for each device
         // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE
         // scenario.
         std::optional<nsecs_t> downTimeInTarget;
-        std::bitset<MAX_POINTER_ID + 1> hoveringPointerIds;
+        std::vector<PointerProperties> hoveringPointers;
 
-        bool hasPointers() const { return touchingPointerIds.any() || hoveringPointerIds.any(); };
+        bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); };
     };
 
     std::map<DeviceId, DeviceState> mDeviceStates;
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 001dc6c..653f595 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -92,14 +92,17 @@
      * This method may be called on any thread (usually by the input manager).
      */
     virtual void setFocusedApplication(
-            int32_t displayId,
+            ui::LogicalDisplayId displayId,
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) = 0;
 
     /* Sets the focused display.
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual void setFocusedDisplay(int32_t displayId) = 0;
+    virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0;
+
+    /** Sets the minimum time between user activity pokes. */
+    virtual void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) = 0;
 
     /* Sets the input dispatching mode.
      *
@@ -127,7 +130,7 @@
      * Returns true when changing touch mode state.
      */
     virtual bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission,
-                                int32_t displayId) = 0;
+                                ui::LogicalDisplayId displayId) = 0;
 
     /**
      * Sets the maximum allowed obscuring opacity by UID to propagate touches.
@@ -137,19 +140,24 @@
      */
     virtual void setMaximumObscuringOpacityForTouch(float opacity) = 0;
 
-    /* Transfers touch focus from one window to another window.
+    /**
+     * Transfers a touch gesture from one window to another window. Transferring touch will not
+     * have any effect on the focused window.
      *
-     * Returns true on success.  False if the window did not actually have touch focus.
+     * Returns true on success.  False if the window did not actually have an active touch gesture.
      */
-    virtual bool transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
-                                    bool isDragDrop) = 0;
+    virtual bool transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
+                                      bool isDragDrop) = 0;
 
     /**
-     * Transfer touch focus to the provided channel, no matter where the current touch is.
+     * Transfer a touch gesture to the provided channel, no matter where the current touch is.
+     * Transferring touch will not have any effect on the focused window.
      *
-     * Return true on success, false if there was no on-going touch.
+     * Returns true on success, false if there was no on-going touch on the display.
+     * @deprecated
      */
-    virtual bool transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) = 0;
+    virtual bool transferTouchOnDisplay(const sp<IBinder>& destChannelToken,
+                                        ui::LogicalDisplayId displayId) = 0;
 
     /**
      * Sets focus on the specified window.
@@ -172,9 +180,8 @@
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
-                                                                           const std::string& name,
-                                                                           gui::Pid pid) = 0;
+    virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(
+            ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) = 0;
 
     /* Removes input channels that will no longer receive input events.
      *
@@ -200,7 +207,8 @@
      * 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;
+    virtual void setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId,
+                                                        bool isEligible) = 0;
 
     /* Flush input device motion sensor.
      *
@@ -211,7 +219,7 @@
     /**
      * Called when a display has been removed from the system.
      */
-    virtual void displayRemoved(int32_t displayId) = 0;
+    virtual void displayRemoved(ui::LogicalDisplayId displayId) = 0;
 
     /*
      * Abort the current touch stream.
@@ -227,8 +235,13 @@
     /*
      * Determine if a pointer from a device is being dispatched to the given window.
      */
-    virtual bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
-                                   int32_t pointerId) = 0;
+    virtual bool isPointerInWindow(const sp<IBinder>& token, ui::LogicalDisplayId displayId,
+                                   DeviceId deviceId, int32_t pointerId) = 0;
+
+    /*
+     * Notify the dispatcher that the state of the input method connection changed.
+     */
+    virtual void setInputMethodConnectionIsActive(bool isActive) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 1c23720..0f03620 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -99,7 +99,8 @@
      * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
      * should be dispatched to applications.
      */
-    virtual void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
+    virtual void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source,
+                                               int32_t action, nsecs_t when,
                                                uint32_t& policyFlags) = 0;
 
     /* Allows the policy a chance to intercept a key before dispatching. */
@@ -119,7 +120,8 @@
                               uint32_t policyFlags) = 0;
 
     /* Poke user activity for an event dispatched to a window. */
-    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) = 0;
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                                  ui::LogicalDisplayId displayId) = 0;
 
     /*
      * Return true if the provided event is stale, and false otherwise. Used for determining
@@ -131,6 +133,18 @@
         return std::chrono::nanoseconds(currentTime - eventTime) >= STALE_EVENT_TIMEOUT;
     }
 
+    /**
+     * Get the additional latency to add while waiting for other input events to process before
+     * dispatching the pending key.
+     * If there are unprocessed events, the pending key will not be dispatched immediately. Instead,
+     * the dispatcher will wait for this timeout, to account for the possibility that the focus
+     * might change due to touch or other events (such as another app getting launched by keys).
+     * This would give the pending key the opportunity to go to a newly focused window instead.
+     */
+    virtual std::chrono::nanoseconds getKeyWaitingForEventsTimeout() {
+        return KEY_WAITING_FOR_EVENTS_TIMEOUT;
+    }
+
     /* Notifies the policy that a pointer down event has occurred outside the current focused
      * window.
      *
@@ -150,6 +164,13 @@
     /* Notifies the policy that there was an input device interaction with apps. */
     virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
                                          const std::set<gui::Uid>& uids) = 0;
+
+private:
+    // Additional key latency in case a connection is still processing some motion events.
+    // This will help with the case when a user touched a button that opens a new window,
+    // and gives us the chance to dispatch the key to this new window.
+    static constexpr std::chrono::nanoseconds KEY_WAITING_FOR_EVENTS_TIMEOUT =
+            std::chrono::milliseconds(500);
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
new file mode 100644
index 0000000..2d7554c
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2024 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 "AndroidInputEventProtoConverter.h"
+
+#include <android-base/logging.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+
+namespace android::inputdispatcher::trace {
+
+namespace {
+
+using namespace ftl::flag_operators;
+
+// The trace config to use for maximal tracing.
+const impl::TraceConfig CONFIG_TRACE_ALL{
+        .flags = impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS |
+                impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH,
+        .rules = {impl::TraceRule{.level = impl::TraceLevel::TRACE_LEVEL_COMPLETE,
+                                  .matchAllPackages = {},
+                                  .matchAnyPackages = {},
+                                  .matchSecure{},
+                                  .matchImeConnectionActive = {}}},
+};
+
+} // namespace
+
+void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event,
+                                                         proto::AndroidMotionEvent& outProto,
+                                                         bool isRedacted) {
+    outProto.set_event_id(event.id);
+    outProto.set_event_time_nanos(event.eventTime);
+    outProto.set_down_time_nanos(event.downTime);
+    outProto.set_source(event.source);
+    outProto.set_action(event.action);
+    outProto.set_device_id(event.deviceId);
+    outProto.set_display_id(event.displayId.val());
+    outProto.set_classification(static_cast<int32_t>(event.classification));
+    outProto.set_flags(event.flags);
+    outProto.set_policy_flags(event.policyFlags);
+
+    if (!isRedacted) {
+        outProto.set_cursor_position_x(event.xCursorPosition);
+        outProto.set_cursor_position_y(event.yCursorPosition);
+        outProto.set_meta_state(event.metaState);
+    }
+
+    for (uint32_t i = 0; i < event.pointerProperties.size(); i++) {
+        auto* pointer = outProto.add_pointer();
+
+        const auto& props = event.pointerProperties[i];
+        pointer->set_pointer_id(props.id);
+        pointer->set_tool_type(static_cast<int32_t>(props.toolType));
+
+        const auto& coords = event.pointerCoords[i];
+        auto bits = BitSet64(coords.bits);
+        for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
+            const auto axis = bits.clearFirstMarkedBit();
+            auto axisEntry = pointer->add_axis_value();
+            axisEntry->set_axis(axis);
+
+            if (!isRedacted) {
+                axisEntry->set_value(coords.values[axisIndex]);
+            }
+        }
+    }
+}
+
+void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& event,
+                                                      proto::AndroidKeyEvent& outProto,
+                                                      bool isRedacted) {
+    outProto.set_event_id(event.id);
+    outProto.set_event_time_nanos(event.eventTime);
+    outProto.set_down_time_nanos(event.downTime);
+    outProto.set_source(event.source);
+    outProto.set_action(event.action);
+    outProto.set_device_id(event.deviceId);
+    outProto.set_display_id(event.displayId.val());
+    outProto.set_repeat_count(event.repeatCount);
+    outProto.set_flags(event.flags);
+    outProto.set_policy_flags(event.policyFlags);
+
+    if (!isRedacted) {
+        outProto.set_key_code(event.keyCode);
+        outProto.set_scan_code(event.scanCode);
+        outProto.set_meta_state(event.metaState);
+    }
+}
+
+void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(
+        const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto,
+        bool isRedacted) {
+    std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
+    outProto.set_vsync_id(args.vsyncId);
+    outProto.set_window_id(args.windowId);
+    outProto.set_resolved_flags(args.resolvedFlags);
+
+    if (isRedacted) {
+        return;
+    }
+    if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) {
+        for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
+            auto* pointerProto = outProto.add_dispatched_pointer();
+            pointerProto->set_pointer_id(motion->pointerProperties[i].id);
+            const auto rawXY =
+                    MotionEvent::calculateTransformedXY(motion->source, args.rawTransform,
+                                                        motion->pointerCoords[i].getXYValue());
+            pointerProto->set_x_in_display(rawXY.x);
+            pointerProto->set_y_in_display(rawXY.y);
+
+            const auto& coords = motion->pointerCoords[i];
+            const auto coordsInWindow =
+                    MotionEvent::calculateTransformedCoords(motion->source, args.transform, coords);
+            auto bits = BitSet64(coords.bits);
+            for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
+                const uint32_t axis = bits.clearFirstMarkedBit();
+                const float axisValueInWindow = coordsInWindow.values[axisIndex];
+                if (coords.values[axisIndex] != axisValueInWindow) {
+                    auto* axisEntry = pointerProto->add_axis_value_in_window();
+                    axisEntry->set_axis(axis);
+                    axisEntry->set_value(axisValueInWindow);
+                }
+            }
+        }
+    }
+}
+
+impl::TraceConfig AndroidInputEventProtoConverter::parseConfig(
+        proto::AndroidInputEventConfig::Decoder& protoConfig) {
+    if (protoConfig.has_mode() &&
+        protoConfig.mode() == proto::AndroidInputEventConfig::TRACE_MODE_TRACE_ALL) {
+        // User has requested the preset for maximal tracing
+        return CONFIG_TRACE_ALL;
+    }
+
+    impl::TraceConfig config;
+
+    // Parse trace flags
+    if (protoConfig.has_trace_dispatcher_input_events() &&
+        protoConfig.trace_dispatcher_input_events()) {
+        config.flags |= impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS;
+    }
+    if (protoConfig.has_trace_dispatcher_window_dispatch() &&
+        protoConfig.trace_dispatcher_window_dispatch()) {
+        config.flags |= impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH;
+    }
+
+    // Parse trace rules
+    auto rulesIt = protoConfig.rules();
+    while (rulesIt) {
+        proto::AndroidInputEventConfig::TraceRule::Decoder protoRule{rulesIt->as_bytes()};
+        config.rules.emplace_back();
+        auto& rule = config.rules.back();
+
+        rule.level = protoRule.has_trace_level()
+                ? static_cast<impl::TraceLevel>(protoRule.trace_level())
+                : impl::TraceLevel::TRACE_LEVEL_NONE;
+
+        if (protoRule.has_match_all_packages()) {
+            auto pkgIt = protoRule.match_all_packages();
+            while (pkgIt) {
+                rule.matchAllPackages.emplace_back(pkgIt->as_std_string());
+                pkgIt++;
+            }
+        }
+
+        if (protoRule.has_match_any_packages()) {
+            auto pkgIt = protoRule.match_any_packages();
+            while (pkgIt) {
+                rule.matchAnyPackages.emplace_back(pkgIt->as_std_string());
+                pkgIt++;
+            }
+        }
+
+        if (protoRule.has_match_secure()) {
+            rule.matchSecure = protoRule.match_secure();
+        }
+
+        if (protoRule.has_match_ime_connection_active()) {
+            rule.matchImeConnectionActive = protoRule.match_ime_connection_active();
+        }
+
+        rulesIt++;
+    }
+
+    return config;
+}
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
new file mode 100644
index 0000000..887913f
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 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 <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+
+#include "InputTracingBackendInterface.h"
+#include "InputTracingPerfettoBackendConfig.h"
+
+namespace proto = perfetto::protos::pbzero;
+
+namespace android::inputdispatcher::trace {
+
+/**
+ * Write traced events into Perfetto protos.
+ */
+class AndroidInputEventProtoConverter {
+public:
+    static void toProtoMotionEvent(const TracedMotionEvent& event,
+                                   proto::AndroidMotionEvent& outProto, bool isRedacted);
+    static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto,
+                                bool isRedacted);
+    static void toProtoWindowDispatchEvent(const WindowDispatchArgs&,
+                                           proto::AndroidWindowInputDispatchEvent& outProto,
+                                           bool isRedacted);
+
+    static impl::TraceConfig parseConfig(proto::AndroidInputEventConfig::Decoder& protoConfig);
+};
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/EventTrackerInterface.h b/services/inputflinger/dispatcher/trace/EventTrackerInterface.h
new file mode 100644
index 0000000..929820e
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/EventTrackerInterface.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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
+
+namespace android::inputdispatcher::trace {
+
+/**
+ * A tracker used to track the lifecycle of a traced input event.
+ * The tracker should be stored inside the traced event. When the event goes out of scope after
+ * the dispatcher has finished processing it, the tracker will call back into the tracer to
+ * initiate cleanup.
+ */
+class EventTrackerInterface {
+public:
+    virtual ~EventTrackerInterface() = default;
+};
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
new file mode 100644
index 0000000..a1a87af
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputTracer"
+
+#include "InputTracer.h"
+
+#include <android-base/logging.h>
+#include <private/android_filesystem_config.h>
+
+namespace android::inputdispatcher::trace::impl {
+
+namespace {
+
+// Helper to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... {
+    using V::operator()...;
+};
+
+TracedEvent createTracedEvent(const MotionEntry& e, EventType type) {
+    return TracedMotionEvent{e.id,
+                             e.eventTime,
+                             e.policyFlags,
+                             e.deviceId,
+                             e.source,
+                             e.displayId,
+                             e.action,
+                             e.actionButton,
+                             e.flags,
+                             e.metaState,
+                             e.buttonState,
+                             e.classification,
+                             e.edgeFlags,
+                             e.xPrecision,
+                             e.yPrecision,
+                             e.xCursorPosition,
+                             e.yCursorPosition,
+                             e.downTime,
+                             e.pointerProperties,
+                             e.pointerCoords,
+                             type};
+}
+
+TracedEvent createTracedEvent(const KeyEntry& e, EventType type) {
+    return TracedKeyEvent{e.id,        e.eventTime, e.policyFlags, e.deviceId, e.source,
+                          e.displayId, e.action,    e.keyCode,     e.scanCode, e.metaState,
+                          e.downTime,  e.flags,     e.repeatCount, type};
+}
+
+void writeEventToBackend(const TracedEvent& event, const TracedEventMetadata metadata,
+                         InputTracingBackendInterface& backend) {
+    std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e, metadata); },
+                       [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e, metadata); }},
+               event);
+}
+
+inline auto getId(const trace::TracedEvent& v) {
+    return std::visit([](const auto& event) { return event.id; }, v);
+}
+
+// Helper class to extract relevant information from InputTarget.
+struct InputTargetInfo {
+    gui::Uid uid;
+    bool isSecureWindow;
+};
+
+InputTargetInfo getTargetInfo(const InputTarget& target) {
+    if (target.windowHandle == nullptr) {
+        if (!target.connection->monitor) {
+            LOG(FATAL) << __func__ << ": Window is not set for non-monitor target";
+        }
+        // This is a global monitor, assume its target is the system.
+        return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false};
+    }
+    const auto& info = *target.windowHandle->getInfo();
+    const bool isSensitiveTarget =
+            info.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY);
+    return {target.windowHandle->getInfo()->ownerUid, isSensitiveTarget};
+}
+
+} // namespace
+
+// --- InputTracer ---
+
+InputTracer::InputTracer(std::unique_ptr<InputTracingBackendInterface> backend)
+      : mBackend(std::move(backend)) {}
+
+std::unique_ptr<EventTrackerInterface> InputTracer::traceInboundEvent(const EventEntry& entry) {
+    // This is a newly traced inbound event. Create a new state to track it and its derived events.
+    auto eventState = std::make_shared<EventState>(*this);
+
+    if (entry.type == EventEntry::Type::MOTION) {
+        const auto& motion = static_cast<const MotionEntry&>(entry);
+        eventState->events.emplace_back(createTracedEvent(motion, EventType::INBOUND));
+    } else if (entry.type == EventEntry::Type::KEY) {
+        const auto& key = static_cast<const KeyEntry&>(entry);
+        eventState->events.emplace_back(createTracedEvent(key, EventType::INBOUND));
+    } else {
+        LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
+    }
+
+    return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/false);
+}
+
+std::unique_ptr<EventTrackerInterface> InputTracer::createTrackerForSyntheticEvent() {
+    // Create a new EventState to track events derived from this tracker.
+    return std::make_unique<EventTrackerImpl>(std::make_shared<EventState>(*this),
+                                              /*isDerived=*/false);
+}
+
+void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie,
+                                       const InputTarget& target) {
+    auto& eventState = getState(cookie);
+    const InputTargetInfo& targetInfo = getTargetInfo(target);
+    if (eventState->isEventProcessingComplete) {
+        // Disallow adding new targets after eventProcessingComplete() is called.
+        if (eventState->metadata.targets.count(targetInfo.uid) == 0) {
+            LOG(FATAL) << __func__ << ": Cannot add new target after eventProcessingComplete";
+        }
+        return;
+    }
+    if (isDerivedCookie(cookie)) {
+        // Disallow adding new targets from a derived cookie.
+        if (eventState->metadata.targets.count(targetInfo.uid) == 0) {
+            LOG(FATAL) << __func__ << ": Cannot add new target from a derived cookie";
+        }
+        return;
+    }
+
+    eventState->metadata.targets.emplace(targetInfo.uid);
+    eventState->metadata.isSecure |= targetInfo.isSecureWindow;
+}
+
+void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
+    if (isDerivedCookie(cookie)) {
+        LOG(FATAL) << "Event processing cannot be set from a derived cookie.";
+    }
+    auto& eventState = getState(cookie);
+    if (eventState->isEventProcessingComplete) {
+        LOG(FATAL) << "Traced event was already logged. "
+                      "eventProcessingComplete() was likely called more than once.";
+    }
+    eventState->onEventProcessingComplete();
+}
+
+std::unique_ptr<EventTrackerInterface> InputTracer::traceDerivedEvent(
+        const EventEntry& entry, const EventTrackerInterface& originalEventCookie) {
+    // This is an event derived from an already-established event. Use the same state to track
+    // this event too.
+    auto eventState = getState(originalEventCookie);
+
+    if (entry.type == EventEntry::Type::MOTION) {
+        const auto& motion = static_cast<const MotionEntry&>(entry);
+        eventState->events.emplace_back(createTracedEvent(motion, EventType::SYNTHESIZED));
+    } else if (entry.type == EventEntry::Type::KEY) {
+        const auto& key = static_cast<const KeyEntry&>(entry);
+        eventState->events.emplace_back(createTracedEvent(key, EventType::SYNTHESIZED));
+    } else {
+        LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
+    }
+
+    if (eventState->isEventProcessingComplete) {
+        // It is possible for a derived event to be dispatched some time after the original event
+        // is dispatched, such as in the case of key fallback events. To account for these cases,
+        // derived events can be traced after the processing is complete for the original event.
+        const auto& event = eventState->events.back();
+        writeEventToBackend(event, eventState->metadata, *mBackend);
+    }
+    return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/true);
+}
+
+void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry,
+                                     const EventTrackerInterface& cookie) {
+    auto& eventState = getState(cookie);
+    const EventEntry& entry = *dispatchEntry.eventEntry;
+    const int32_t eventId = entry.id;
+    // TODO(b/328618922): Remove resolved key repeats after making repeatCount non-mutable.
+    // The KeyEntry's repeatCount is mutable and can be modified after an event is initially traced,
+    // so we need to find the repeatCount at the time of dispatching to trace it accurately.
+    int32_t resolvedKeyRepeatCount = 0;
+    if (entry.type == EventEntry::Type::KEY) {
+        resolvedKeyRepeatCount = static_cast<const KeyEntry&>(entry).repeatCount;
+    }
+
+    auto tracedEventIt =
+            std::find_if(eventState->events.begin(), eventState->events.end(),
+                         [eventId](const auto& event) { return eventId == getId(event); });
+    if (tracedEventIt == eventState->events.end()) {
+        LOG(FATAL)
+                << __func__
+                << ": Failed to find a previously traced event that matches the dispatched event";
+    }
+
+    if (eventState->metadata.targets.count(dispatchEntry.targetUid) == 0) {
+        LOG(FATAL) << __func__ << ": Event is being dispatched to UID that it is not targeting";
+    }
+
+    // The vsyncId only has meaning if the event is targeting a window.
+    const int32_t windowId = dispatchEntry.windowId.value_or(0);
+    const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0;
+
+    // TODO(b/210460522): Pass HMAC into traceEventDispatch.
+    const WindowDispatchArgs windowDispatchArgs{*tracedEventIt,
+                                                dispatchEntry.deliveryTime,
+                                                dispatchEntry.resolvedFlags,
+                                                dispatchEntry.targetUid,
+                                                vsyncId,
+                                                windowId,
+                                                dispatchEntry.transform,
+                                                dispatchEntry.rawTransform,
+                                                /*hmac=*/{},
+                                                resolvedKeyRepeatCount};
+    if (eventState->isEventProcessingComplete) {
+        mBackend->traceWindowDispatch(std::move(windowDispatchArgs), eventState->metadata);
+    } else {
+        eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs));
+    }
+}
+
+std::shared_ptr<InputTracer::EventState>& InputTracer::getState(
+        const EventTrackerInterface& cookie) {
+    return static_cast<const EventTrackerImpl&>(cookie).mState;
+}
+
+bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) {
+    return static_cast<const EventTrackerImpl&>(cookie).mIsDerived;
+}
+
+// --- InputTracer::EventState ---
+
+void InputTracer::EventState::onEventProcessingComplete() {
+    metadata.isImeConnectionActive = tracer.mIsImeConnectionActive;
+
+    // Write all of the events known so far to the trace.
+    for (const auto& event : events) {
+        writeEventToBackend(event, metadata, *tracer.mBackend);
+    }
+    // Write all pending dispatch args to the trace.
+    for (const auto& windowDispatchArgs : pendingDispatchArgs) {
+        auto tracedEventIt =
+                std::find_if(events.begin(), events.end(),
+                             [id = getId(windowDispatchArgs.eventEntry)](const auto& event) {
+                                 return id == getId(event);
+                             });
+        if (tracedEventIt == events.end()) {
+            LOG(FATAL) << __func__
+                       << ": Failed to find a previously traced event that matches the dispatched "
+                          "event";
+        }
+        tracer.mBackend->traceWindowDispatch(windowDispatchArgs, metadata);
+    }
+    pendingDispatchArgs.clear();
+
+    isEventProcessingComplete = true;
+}
+
+InputTracer::EventState::~EventState() {
+    if (isEventProcessingComplete) {
+        // This event has already been written to the trace as expected.
+        return;
+    }
+    // The event processing was never marked as complete, so do it now.
+    // We should never end up here in normal operation. However, in tests, it's possible that we
+    // stop and destroy InputDispatcher without waiting for it to finish processing events, at
+    // which point an event (and thus its EventState) may be destroyed before processing finishes.
+    onEventProcessingComplete();
+}
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
new file mode 100644
index 0000000..ab175be
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 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 "InputTracerInterface.h"
+
+#include <memory>
+
+#include "../Entry.h"
+#include "InputTracingBackendInterface.h"
+
+namespace android::inputdispatcher::trace::impl {
+
+/**
+ * The tracer implementation for InputDispatcher.
+ *
+ * InputTracer's responsibility is to keep track of events as they are processed by InputDispatcher,
+ * and to write the events to the tracing backend when enough information is collected. InputTracer
+ * is not thread-safe.
+ *
+ * See the documentation in InputTracerInterface for the API surface.
+ */
+class InputTracer : public InputTracerInterface {
+public:
+    explicit InputTracer(std::unique_ptr<InputTracingBackendInterface>);
+    ~InputTracer() = default;
+    InputTracer(const InputTracer&) = delete;
+    InputTracer& operator=(const InputTracer&) = delete;
+
+    std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) override;
+    std::unique_ptr<EventTrackerInterface> createTrackerForSyntheticEvent() override;
+    void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override;
+    void eventProcessingComplete(const EventTrackerInterface&) override;
+    std::unique_ptr<EventTrackerInterface> traceDerivedEvent(const EventEntry&,
+                                                             const EventTrackerInterface&) override;
+    void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) override;
+    void setInputMethodConnectionIsActive(bool isActive) override {
+        mIsImeConnectionActive = isActive;
+    }
+
+private:
+    std::unique_ptr<InputTracingBackendInterface> mBackend;
+    bool mIsImeConnectionActive{false};
+
+    // The state of a tracked event, shared across all events derived from the original event.
+    struct EventState {
+        explicit inline EventState(InputTracer& tracer) : tracer(tracer){};
+        ~EventState();
+
+        void onEventProcessingComplete();
+
+        InputTracer& tracer;
+        std::vector<const TracedEvent> events;
+        bool isEventProcessingComplete{false};
+        // A queue to hold dispatch args from being traced until event processing is complete.
+        std::vector<const WindowDispatchArgs> pendingDispatchArgs;
+        // The metadata should not be modified after event processing is complete.
+        TracedEventMetadata metadata{};
+    };
+
+    // Get the event state associated with a tracking cookie.
+    std::shared_ptr<EventState>& getState(const EventTrackerInterface&);
+    bool isDerivedCookie(const EventTrackerInterface&);
+
+    // Implementation of the event tracker cookie. The cookie holds the event state directly for
+    // convenience to avoid the overhead of tracking the state separately in InputTracer.
+    class EventTrackerImpl : public EventTrackerInterface {
+    public:
+        inline EventTrackerImpl(const std::shared_ptr<EventState>& state, bool isDerivedEvent)
+              : mState(state), mIsDerived(isDerivedEvent) {}
+        EventTrackerImpl(const EventTrackerImpl&) = default;
+
+    private:
+        mutable std::shared_ptr<EventState> mState;
+        const bool mIsDerived;
+
+        friend std::shared_ptr<EventState>& InputTracer::getState(const EventTrackerInterface&);
+        friend bool InputTracer::isDerivedCookie(const EventTrackerInterface&);
+    };
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
new file mode 100644
index 0000000..af6eefb
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2024 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 "../Entry.h"
+#include "../InputTarget.h"
+#include "EventTrackerInterface.h"
+
+namespace android::inputdispatcher::trace {
+
+/**
+ * InputTracerInterface is the tracing interface for InputDispatcher.
+ *
+ * The tracer is responsible for tracing information about input events and where they are
+ * dispatched. The trace is logged to the backend using the InputTracingBackendInterface.
+ *
+ * A normal traced event should have the following lifecycle:
+ *  - The EventTracker is obtained from traceInboundEvent(), after which point the event
+ *    should not change.
+ *  - While the event is being processed, dispatchToTargetHint() is called for each target that
+ *    the event will be eventually sent to.
+ *  - Once all targets have been determined, eventProcessingComplete() is called, at which point
+ *    the tracer will have enough information to commit the event to the trace.
+ *  - For each event that is dispatched to the client, traceEventDispatch() is called, and the
+ *    tracer will record that the event was sent to the client.
+ */
+class InputTracerInterface {
+public:
+    InputTracerInterface() = default;
+    virtual ~InputTracerInterface() = default;
+    InputTracerInterface(const InputTracerInterface&) = delete;
+    InputTracerInterface& operator=(const InputTracerInterface&) = delete;
+
+    /**
+     * Trace an input event that is being processed by InputDispatcher. The event must not be
+     * modified after it is traced to keep the traced event consistent with the event that is
+     * eventually dispatched. An EventTracker is returned for each traced event that should be used
+     * to track the event's lifecycle inside InputDispatcher.
+     */
+    virtual std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) = 0;
+
+    /**
+     * Create a trace tracker for a synthetic event that does not stem from an inbound input event.
+     * This includes things like generating cancellations or down events for various reasons,
+     * such as ANR, pilfering, transfer touch, etc. Any key or motion events generated for this
+     * synthetic event should be traced as a derived event using {@link #traceDerivedEvent}.
+     */
+    virtual std::unique_ptr<EventTrackerInterface> createTrackerForSyntheticEvent() = 0;
+
+    /**
+     * Notify the tracer that the traced event will be sent to the given InputTarget.
+     * The tracer may change how the event is logged depending on the target. For example,
+     * events targeting certain UIDs may be logged as sensitive events.
+     * This may be called 0 or more times for each tracked event before event processing is
+     * completed.
+     */
+    virtual void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) = 0;
+
+    /**
+     * Notify the tracer that the event processing is complete. This may be called at most once
+     * for each traced event. If a tracked event is dropped before it can be processed, it is
+     * possible that this is never called before the EventTracker is destroyed.
+     *
+     * This is used to commit the event to the trace in a timely manner, rather than always
+     * waiting for the event to go out of scope (and thus for the EventTracker to be destroyed)
+     * before committing. The point at which the event is destroyed can depend on several factors
+     * outside of our control, such as how long apps take to respond, so we don't want to depend on
+     * that.
+     */
+    virtual void eventProcessingComplete(const EventTrackerInterface&) = 0;
+
+    /**
+     * Trace an input event that is derived from another event. This is used in cases where an event
+     * is modified from the original, such as when a touch is split across multiple windows, or
+     * when a HOVER_MOVE event is modified to be a HOVER_EXIT, etc. The original event's tracker
+     * must be provided, and a new EventTracker is returned that should be used to track the event's
+     * lifecycle.
+     *
+     * NOTE: The derived tracker cannot be used to change the targets of the original event, meaning
+     * it cannot be used with {@link #dispatchToTargetHint} or {@link eventProcessingComplete}.
+     */
+    virtual std::unique_ptr<EventTrackerInterface> traceDerivedEvent(
+            const EventEntry&, const EventTrackerInterface& originalEventTracker) = 0;
+
+    /**
+     * Trace an input event being successfully dispatched to a window. The dispatched event may
+     * be a previously traced inbound event, or it may be a synthesized event. All dispatched events
+     * must have been previously traced, so the trace tracker associated with the event must be
+     * provided.
+     */
+    virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) = 0;
+
+    /**
+     * Notify that the state of the input method connection changed.
+     */
+    virtual void setInputMethodConnectionIsActive(bool isActive) = 0;
+};
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
new file mode 100644
index 0000000..25099c3
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2024 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 <gui/PidUid.h>
+#include <input/Input.h>
+#include <ui/Transform.h>
+
+#include <array>
+#include <set>
+#include <variant>
+#include <vector>
+
+namespace android::inputdispatcher::trace {
+
+/**
+ * Describes the type of this event being traced, with respect to InputDispatcher.
+ */
+enum class EventType {
+    // This is an event that was reported through the InputListener interface or was injected.
+    INBOUND,
+    // This is an event that was synthesized within InputDispatcher; either being derived
+    // from an inbound event (e.g. a split motion event), or synthesized completely
+    // (e.g. a CANCEL event generated when the inbound stream is not canceled).
+    SYNTHESIZED,
+
+    ftl_last = SYNTHESIZED,
+};
+
+/**
+ * A representation of an Android KeyEvent used by the tracing backend.
+ */
+struct TracedKeyEvent {
+    int32_t id;
+    nsecs_t eventTime;
+    uint32_t policyFlags;
+    int32_t deviceId;
+    uint32_t source;
+    ui::LogicalDisplayId displayId;
+    int32_t action;
+    int32_t keyCode;
+    int32_t scanCode;
+    int32_t metaState;
+    nsecs_t downTime;
+    int32_t flags;
+    int32_t repeatCount;
+    EventType eventType;
+};
+
+/**
+ * A representation of an Android MotionEvent used by the tracing backend.
+ */
+struct TracedMotionEvent {
+    int32_t id;
+    nsecs_t eventTime;
+    uint32_t policyFlags;
+    int32_t deviceId;
+    uint32_t source;
+    ui::LogicalDisplayId displayId;
+    int32_t action;
+    int32_t actionButton;
+    int32_t flags;
+    int32_t metaState;
+    int32_t buttonState;
+    MotionClassification classification;
+    int32_t edgeFlags;
+    float xPrecision;
+    float yPrecision;
+    float xCursorPosition;
+    float yCursorPosition;
+    nsecs_t downTime;
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+    EventType eventType;
+};
+
+/** A representation of a traced input event. */
+using TracedEvent = std::variant<TracedKeyEvent, TracedMotionEvent>;
+
+/** Additional information about an input event being traced. */
+struct TracedEventMetadata {
+    // True if the event is targeting at least one secure window.
+    bool isSecure;
+    // The list of possible UIDs that this event could be targeting.
+    std::set<gui::Uid> targets;
+    // True if the there was an active input method connection while this event was processed.
+    bool isImeConnectionActive;
+};
+
+/** Additional information about an input event being dispatched to a window. */
+struct WindowDispatchArgs {
+    TracedEvent eventEntry;
+    nsecs_t deliveryTime;
+    int32_t resolvedFlags;
+    gui::Uid targetUid;
+    int64_t vsyncId;
+    int32_t windowId;
+    ui::Transform transform;
+    ui::Transform rawTransform;
+    std::array<uint8_t, 32> hmac;
+    int32_t resolvedKeyRepeatCount;
+};
+
+/**
+ * An interface for the tracing backend, used for setting a custom backend for testing.
+ */
+class InputTracingBackendInterface {
+public:
+    virtual ~InputTracingBackendInterface() = default;
+
+    /** Trace a KeyEvent. */
+    virtual void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) = 0;
+
+    /** Trace a MotionEvent. */
+    virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) = 0;
+
+    /** Trace an event being sent to a window. */
+    virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) = 0;
+};
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
new file mode 100644
index 0000000..9b9633a
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputTracer"
+
+#include "InputTracingPerfettoBackend.h"
+
+#include "AndroidInputEventProtoConverter.h"
+
+#include <android-base/logging.h>
+#include <binder/IServiceManager.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <private/android_filesystem_config.h>
+#include <utils/String16.h>
+
+namespace android::inputdispatcher::trace::impl {
+
+namespace {
+
+constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent";
+
+bool isPermanentlyAllowed(gui::Uid uid) {
+    switch (uid.val()) {
+        case AID_SYSTEM:
+        case AID_SHELL:
+        case AID_ROOT:
+            return true;
+        default:
+            return false;
+    }
+}
+
+sp<content::pm::IPackageManagerNative> getPackageManager() {
+    sp<IServiceManager> serviceManager = defaultServiceManager();
+    if (!serviceManager) {
+        LOG(ERROR) << __func__ << ": unable to access native ServiceManager";
+        return nullptr;
+    }
+
+    sp<IBinder> binder = serviceManager->waitForService(String16("package_native"));
+    auto packageManager = interface_cast<content::pm::IPackageManagerNative>(binder);
+    if (!packageManager) {
+        LOG(ERROR) << ": unable to access native PackageManager";
+        return nullptr;
+    }
+    return packageManager;
+}
+
+gui::Uid getPackageUid(const sp<content::pm::IPackageManagerNative>& pm,
+                       const std::string& package) {
+    int32_t outUid = -1;
+    if (auto status = pm->getPackageUid(package, /*flags=*/0, AID_SYSTEM, &outUid);
+        !status.isOk()) {
+        LOG(INFO) << "Failed to get package UID from native package manager for package '"
+                  << package << "': " << status;
+        return gui::Uid::INVALID;
+    }
+    return gui::Uid{static_cast<uid_t>(outUid)};
+}
+
+} // namespace
+
+// --- PerfettoBackend::InputEventDataSource ---
+
+PerfettoBackend::InputEventDataSource::InputEventDataSource() : mInstanceId(sNextInstanceId++) {}
+
+void PerfettoBackend::InputEventDataSource::OnSetup(const InputEventDataSource::SetupArgs& args) {
+    LOG(INFO) << "Setting up perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
+    const auto rawConfig = args.config->android_input_event_config_raw();
+    auto protoConfig = perfetto::protos::pbzero::AndroidInputEventConfig::Decoder{rawConfig};
+
+    mConfig = AndroidInputEventProtoConverter::parseConfig(protoConfig);
+}
+
+void PerfettoBackend::InputEventDataSource::OnStart(const InputEventDataSource::StartArgs&) {
+    LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
+}
+
+void PerfettoBackend::InputEventDataSource::OnStop(const InputEventDataSource::StopArgs&) {
+    LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
+    InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); });
+}
+
+void PerfettoBackend::InputEventDataSource::initializeUidMap() {
+    if (mUidMap.has_value()) {
+        return;
+    }
+
+    mUidMap = {{}};
+    auto packageManager = PerfettoBackend::sPackageManagerProvider();
+    if (!packageManager) {
+        LOG(ERROR) << "Failed to initialize UID map: Could not get native package manager";
+        return;
+    }
+
+    for (const auto& rule : mConfig.rules) {
+        for (const auto& package : rule.matchAllPackages) {
+            mUidMap->emplace(package, getPackageUid(packageManager, package));
+        }
+        for (const auto& package : rule.matchAnyPackages) {
+            mUidMap->emplace(package, getPackageUid(packageManager, package));
+        }
+    }
+}
+
+bool PerfettoBackend::InputEventDataSource::shouldIgnoreTracedInputEvent(
+        const EventType& type) const {
+    if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS)) {
+        // Ignore all input events.
+        return true;
+    }
+    if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH) &&
+        type != EventType::INBOUND) {
+        // When window dispatch tracing is disabled, ignore any events that are not inbound events.
+        return true;
+    }
+    return false;
+}
+
+TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel(
+        const TracedEventMetadata& metadata) const {
+    // Check for matches with the rules in the order that they are defined.
+    for (const auto& rule : mConfig.rules) {
+        if (ruleMatches(rule, metadata)) {
+            return rule.level;
+        }
+    }
+    // The event is not traced if it matched zero rules.
+    return TraceLevel::TRACE_LEVEL_NONE;
+}
+
+bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule,
+                                                        const TracedEventMetadata& metadata) const {
+    // By default, a rule will match all events. Return early if the rule does not match.
+
+    // Match the event if it is directed to a secure window.
+    if (rule.matchSecure.has_value() && *rule.matchSecure != metadata.isSecure) {
+        return false;
+    }
+
+    // Match the event if it was processed while there was an active InputMethod connection.
+    if (rule.matchImeConnectionActive.has_value() &&
+        *rule.matchImeConnectionActive != metadata.isImeConnectionActive) {
+        return false;
+    }
+
+    // Match the event if all of its target packages are explicitly allowed in the "match all" list.
+    if (!rule.matchAllPackages.empty() &&
+        !std::all_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) {
+            return isPermanentlyAllowed(uid) ||
+                    std::any_of(rule.matchAllPackages.begin(), rule.matchAllPackages.end(),
+                                [&](const auto& pkg) { return uid == mUidMap->at(pkg); });
+        })) {
+        return false;
+    }
+
+    // Match the event if any of its target packages are allowed in the "match any" list.
+    if (!rule.matchAnyPackages.empty() &&
+        !std::any_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) {
+            return std::any_of(rule.matchAnyPackages.begin(), rule.matchAnyPackages.end(),
+                               [&](const auto& pkg) { return uid == mUidMap->at(pkg); });
+        })) {
+        return false;
+    }
+
+    // The event matches all matchers specified in the rule.
+    return true;
+}
+
+// --- PerfettoBackend ---
+
+bool PerfettoBackend::sUseInProcessBackendForTest{false};
+
+std::function<sp<content::pm::IPackageManagerNative>()> PerfettoBackend::sPackageManagerProvider{
+        &getPackageManager};
+
+std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
+
+std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
+
+PerfettoBackend::PerfettoBackend() {
+    // Use a once-flag to ensure that the data source is only registered once per boot, since
+    // we never unregister the InputEventDataSource.
+    std::call_once(sDataSourceRegistrationFlag, []() {
+        perfetto::TracingInitArgs args;
+        args.backends = sUseInProcessBackendForTest ? perfetto::kInProcessBackend
+                                                    : perfetto::kSystemBackend;
+        perfetto::Tracing::Initialize(args);
+
+        // Register our custom data source for input event tracing.
+        perfetto::DataSourceDescriptor dsd;
+        dsd.set_name(INPUT_EVENT_TRACE_DATA_SOURCE_NAME);
+        InputEventDataSource::Register(dsd);
+        LOG(INFO) << "InputTracer initialized for data source: "
+                  << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+    });
+}
+
+void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event,
+                                       const TracedEventMetadata& metadata) {
+    InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
+        if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
+        auto tracePacket = ctx.NewTracePacket();
+        auto* inputEvent = tracePacket->set_android_input_event();
+        auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted()
+                                          : inputEvent->set_dispatcher_motion_event();
+        AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted);
+    });
+}
+
+void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event,
+                                    const TracedEventMetadata& metadata) {
+    InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
+        if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
+        auto tracePacket = ctx.NewTracePacket();
+        auto* inputEvent = tracePacket->set_android_input_event();
+        auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted()
+                                       : inputEvent->set_dispatcher_key_event();
+        AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted);
+    });
+}
+
+void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
+                                          const TracedEventMetadata& metadata) {
+    InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
+        if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
+        auto tracePacket = ctx.NewTracePacket();
+        auto* inputEvent = tracePacket->set_android_input_event();
+        auto* dispatchEvent = isRedacted
+                ? inputEvent->set_dispatcher_window_dispatch_event_redacted()
+                : inputEvent->set_dispatcher_window_dispatch_event();
+        AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent,
+                                                                    isRedacted);
+    });
+}
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
new file mode 100644
index 0000000..d0bab06
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 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 "InputTracingBackendInterface.h"
+
+#include "InputTracingPerfettoBackendConfig.h"
+
+#include <android/content/pm/IPackageManagerNative.h>
+#include <ftl/flags.h>
+#include <perfetto/tracing.h>
+#include <mutex>
+#include <set>
+
+namespace android::inputdispatcher::trace::impl {
+
+/**
+ * The tracing backend that writes events into ongoing Perfetto traces.
+ *
+ * Example shell command to take an input trace from Perfetto:
+ *
+ *   adb shell  perfetto \
+ *    -c - --txt \
+ *    -o /data/misc/perfetto-traces/trace.input-trace \
+ *    <<END
+ *    buffers: {
+ *      size_kb: 5000
+ *      fill_policy: RING_BUFFER
+ *    }
+ *    data_sources: {
+ *      config {
+ *          name: "android.input.inputevent"
+ *      }
+ *    }
+ *    END
+ */
+class PerfettoBackend : public InputTracingBackendInterface {
+public:
+    static bool sUseInProcessBackendForTest;
+    static std::function<sp<content::pm::IPackageManagerNative>()> sPackageManagerProvider;
+
+    explicit PerfettoBackend();
+    ~PerfettoBackend() override = default;
+
+    void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override;
+    void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
+    void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
+
+private:
+    // Implementation of the perfetto data source.
+    // Each instance of the InputEventDataSource represents a different tracing session.
+    // Its lifecycle is controlled by perfetto.
+    class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
+    public:
+        explicit InputEventDataSource();
+
+        void OnSetup(const SetupArgs&) override;
+        void OnStart(const StartArgs&) override;
+        void OnStop(const StopArgs&) override;
+
+        void initializeUidMap();
+        bool shouldIgnoreTracedInputEvent(const EventType&) const;
+        inline ftl::Flags<TraceFlag> getFlags() const { return mConfig.flags; }
+        TraceLevel resolveTraceLevel(const TracedEventMetadata&) const;
+
+    private:
+        const int32_t mInstanceId;
+        TraceConfig mConfig;
+
+        bool ruleMatches(const TraceRule&, const TracedEventMetadata&) const;
+
+        std::optional<std::map<std::string, gui::Uid>> mUidMap;
+    };
+
+    static std::once_flag sDataSourceRegistrationFlag;
+    static std::atomic<int32_t> sNextInstanceId;
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h
new file mode 100644
index 0000000..536e32b
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/enum.h>
+#include <ftl/flags.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <vector>
+
+namespace android::inputdispatcher::trace::impl {
+
+/** Flags representing the configurations that are enabled in the trace. */
+enum class TraceFlag : uint32_t {
+    // Trace details about input events processed by InputDispatcher.
+    TRACE_DISPATCHER_INPUT_EVENTS = 0x1,
+    // Trace details about an event being sent to a window by InputDispatcher.
+    TRACE_DISPATCHER_WINDOW_DISPATCH = 0x2,
+
+    ftl_last = TRACE_DISPATCHER_WINDOW_DISPATCH,
+};
+
+/** Representation of AndroidInputEventConfig::TraceLevel. */
+using TraceLevel = perfetto::protos::pbzero::AndroidInputEventConfig::TraceLevel;
+
+/** Representation of AndroidInputEventConfig::TraceRule. */
+struct TraceRule {
+    TraceLevel level;
+
+    std::vector<std::string> matchAllPackages;
+    std::vector<std::string> matchAnyPackages;
+    std::optional<bool> matchSecure;
+    std::optional<bool> matchImeConnectionActive;
+};
+
+/**
+ * A complete configuration for a tracing session.
+ *
+ * The trace rules are applied as documented in the perfetto config:
+ *   /external/perfetto/protos/perfetto/config/android/android_input_event_config.proto
+ */
+struct TraceConfig {
+    ftl::Flags<TraceFlag> flags;
+    std::vector<TraceRule> rules;
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
new file mode 100644
index 0000000..3c3c15a
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputTracer"
+
+#include "ThreadedBackend.h"
+
+#include "InputTracingPerfettoBackend.h"
+
+#include <android-base/logging.h>
+
+namespace android::inputdispatcher::trace::impl {
+
+namespace {
+
+// Helper to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... {
+    using V::operator()...;
+};
+
+} // namespace
+
+// --- ThreadedBackend ---
+
+template <typename Backend>
+ThreadedBackend<Backend>::ThreadedBackend(Backend&& innerBackend)
+      : mBackend(std::move(innerBackend)),
+        mTracerThread(
+                "InputTracer", [this]() { threadLoop(); },
+                [this]() { mThreadWakeCondition.notify_all(); }) {}
+
+template <typename Backend>
+ThreadedBackend<Backend>::~ThreadedBackend() {
+    {
+        std::scoped_lock lock(mLock);
+        mThreadExit = true;
+    }
+    mThreadWakeCondition.notify_all();
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::traceMotionEvent(const TracedMotionEvent& event,
+                                                const TracedEventMetadata& metadata) {
+    std::scoped_lock lock(mLock);
+    mQueue.emplace_back(event, metadata);
+    setIdleStatus(false);
+    mThreadWakeCondition.notify_all();
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::traceKeyEvent(const TracedKeyEvent& event,
+                                             const TracedEventMetadata& metadata) {
+    std::scoped_lock lock(mLock);
+    mQueue.emplace_back(event, metadata);
+    setIdleStatus(false);
+    mThreadWakeCondition.notify_all();
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
+                                                   const TracedEventMetadata& metadata) {
+    std::scoped_lock lock(mLock);
+    mQueue.emplace_back(dispatchArgs, metadata);
+    setIdleStatus(false);
+    mThreadWakeCondition.notify_all();
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::threadLoop() {
+    std::vector<TraceEntry> entries;
+
+    { // acquire lock
+        std::unique_lock lock(mLock);
+        base::ScopedLockAssertion assumeLocked(mLock);
+
+        if (mQueue.empty()) {
+            setIdleStatus(true);
+        }
+
+        // Wait until we need to process more events or exit.
+        mThreadWakeCondition.wait(lock,
+                                  [&]() REQUIRES(mLock) { return mThreadExit || !mQueue.empty(); });
+        if (mThreadExit) {
+            setIdleStatus(true);
+            return;
+        }
+
+        mQueue.swap(entries);
+    } // release lock
+
+    // Trace the events into the backend without holding the lock to reduce the amount of
+    // work performed in the critical section.
+    for (const auto& [entry, traceArgs] : entries) {
+        std::visit(Visitor{[&](const TracedMotionEvent& e) {
+                               mBackend.traceMotionEvent(e, traceArgs);
+                           },
+                           [&](const TracedKeyEvent& e) { mBackend.traceKeyEvent(e, traceArgs); },
+                           [&](const WindowDispatchArgs& args) {
+                               mBackend.traceWindowDispatch(args, traceArgs);
+                           }},
+                   entry);
+    }
+    entries.clear();
+}
+
+template <typename Backend>
+std::function<void()> ThreadedBackend<Backend>::getIdleWaiterForTesting() {
+    std::scoped_lock lock(mLock);
+    if (!mIdleWaiter) {
+        mIdleWaiter = std::make_shared<IdleWaiter>();
+    }
+
+    // Return a lambda that holds a strong reference to the idle waiter, whose lifetime can extend
+    // beyond this threaded backend object.
+    return [idleWaiter = mIdleWaiter]() {
+        std::unique_lock idleLock(idleWaiter->idleLock);
+        base::ScopedLockAssertion assumeLocked(idleWaiter->idleLock);
+        idleWaiter->threadIdleCondition.wait(idleLock, [&]() REQUIRES(idleWaiter->idleLock) {
+            return idleWaiter->isIdle;
+        });
+    };
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::setIdleStatus(bool isIdle) {
+    if (!mIdleWaiter) {
+        return;
+    }
+    std::scoped_lock idleLock(mIdleWaiter->idleLock);
+    mIdleWaiter->isIdle = isIdle;
+    if (isIdle) {
+        mIdleWaiter->threadIdleCondition.notify_all();
+    }
+}
+
+// Explicit template instantiation for the PerfettoBackend.
+template class ThreadedBackend<PerfettoBackend>;
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
new file mode 100644
index 0000000..52a84c4
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 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 "InputThread.h"
+#include "InputTracingPerfettoBackend.h"
+
+#include <android-base/thread_annotations.h>
+#include <mutex>
+#include <variant>
+#include <vector>
+
+namespace android::inputdispatcher::trace::impl {
+
+/**
+ * A wrapper around an InputTracingBackend implementation that writes to the inner tracing backend
+ * from a single new thread that it creates. The new tracing thread is started when the
+ * ThreadedBackend is created, and is stopped when it is destroyed. The ThreadedBackend is
+ * thread-safe.
+ */
+template <typename Backend>
+class ThreadedBackend : public InputTracingBackendInterface {
+public:
+    ThreadedBackend(Backend&& innerBackend);
+    ~ThreadedBackend() override;
+
+    void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override;
+    void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
+    void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
+
+    /** Returns a function that, when called, will block until the tracing thread is idle. */
+    std::function<void()> getIdleWaiterForTesting();
+
+private:
+    std::mutex mLock;
+    bool mThreadExit GUARDED_BY(mLock){false};
+    std::condition_variable mThreadWakeCondition;
+    Backend mBackend;
+    using TraceEntry =
+            std::pair<std::variant<TracedKeyEvent, TracedMotionEvent, WindowDispatchArgs>,
+                      TracedEventMetadata>;
+    std::vector<TraceEntry> mQueue GUARDED_BY(mLock);
+
+    struct IdleWaiter {
+        std::mutex idleLock;
+        std::condition_variable threadIdleCondition;
+        bool isIdle GUARDED_BY(idleLock){false};
+    };
+    // The lazy-initialized object used to wait for the tracing thread to idle.
+    std::shared_ptr<IdleWaiter> mIdleWaiter GUARDED_BY(mLock);
+
+    // InputThread stops when its destructor is called. Initialize it last so that it is the
+    // first thing to be destructed. This will guarantee the thread will not access other
+    // members that have already been destructed.
+    InputThread mTracerThread;
+
+    void threadLoop();
+    void setIdleStatus(bool isIdle) REQUIRES(mLock);
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/include/InputFilterPolicyInterface.h b/services/inputflinger/include/InputFilterPolicyInterface.h
new file mode 100644
index 0000000..4d39b97
--- /dev/null
+++ b/services/inputflinger/include/InputFilterPolicyInterface.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 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
+
+namespace android {
+
+/**
+ * The InputFilter policy interface.
+ *
+ * This is the interface that InputFilter uses to talk to Input Manager and other system components.
+ */
+class InputFilterPolicyInterface {
+public:
+    virtual ~InputFilterPolicyInterface() = default;
+
+    /**
+     * A callback to notify about sticky modifier state changes when Sticky keys feature is enabled.
+     *
+     * modifierState: Current sticky modifier state which will be sent with all subsequent
+     * KeyEvents. This only includes modifiers that can be 'Sticky' which includes: Meta, Ctrl,
+     * Shift, Alt and AltGr.
+     *
+     * lockedModifierState: Current locked modifier state representing modifiers that don't get
+     * cleared after non-modifier key press. This only includes modifiers that can be 'Sticky' which
+     * includes: Meta, Ctrl, Shift, Alt and AltGr.
+     *
+     * For more information {@see sticky_keys_filter.rs}
+     */
+    virtual void notifyStickyModifierStateChanged(uint32_t modifierState,
+                                                  uint32_t lockedModifierState) = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index efc8b26..889ee09 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -61,9 +61,6 @@
         // The display size or orientation changed.
         DISPLAY_INFO = 1u << 2,
 
-        // The visible touches option changed.
-        SHOW_TOUCHES = 1u << 3,
-
         // The keyboard layouts must be reloaded.
         KEYBOARD_LAYOUTS = 1u << 4,
 
@@ -109,11 +106,15 @@
 
     // The associations between input ports and display ports.
     // Used to determine which DisplayViewport should be tied to which InputDevice.
-    std::unordered_map<std::string, uint8_t> portAssociations;
+    std::unordered_map<std::string, uint8_t> inputPortToDisplayPortAssociations;
 
-    // The associations between input device physical port locations and display unique ids.
+    // The associations between input device ports and display unique ids.
     // Used to determine which DisplayViewport should be tied to which InputDevice.
-    std::unordered_map<std::string, std::string> uniqueIdAssociations;
+    std::unordered_map<std::string, std::string> inputPortToDisplayUniqueIdAssociations;
+
+    // The associations between input device descriptor and display unique ids.
+    // Used to determine which DisplayViewport should be tied to which InputDevice.
+    std::unordered_map<std::string, std::string> inputDeviceDescriptorToDisplayUniqueIdAssociations;
 
     // The associations between input device ports device types.
     // This is used to determine which device type and source should be tied to which InputDevice.
@@ -124,9 +125,22 @@
     std::unordered_map<std::string, KeyboardLayoutInfo> keyboardLayoutAssociations;
 
     // The suggested display ID to show the cursor.
-    int32_t defaultPointerDisplayId;
+    ui::LogicalDisplayId defaultPointerDisplayId;
+
+    // The mouse pointer speed, as a number from -7 (slowest) to 7 (fastest).
+    //
+    // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
+    int32_t mousePointerSpeed;
+
+    // Displays on which an acceleration curve shouldn't be applied for pointer movements from mice.
+    //
+    // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
+    std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled;
 
     // Velocity control parameters for mouse pointer movements.
+    //
+    // If the enable_new_mouse_pointer_ballistics flag is enabled, these are ignored and the values
+    // of mousePointerSpeed and mousePointerAccelerationEnabled used instead.
     VelocityControlParameters pointerVelocityControlParameters;
 
     // Velocity control parameters for mouse wheel movements.
@@ -197,9 +211,6 @@
     // will cover this portion of the display diagonal.
     float pointerGestureZoomSpeedRatio;
 
-    // True to show the location of touches on the touch screen as spots.
-    bool showTouches;
-
     // The latest request to enable or disable Pointer Capture.
     PointerCaptureRequest pointerCaptureRequest;
 
@@ -213,6 +224,9 @@
     // True to enable tap-to-click on touchpads.
     bool touchpadTapToClickEnabled;
 
+    // True to enable tap dragging on touchpads.
+    bool touchpadTapDraggingEnabled;
+
     // True to enable a zone on the right-hand side of touchpads where clicks will be turned into
     // context (a.k.a. "right") clicks.
     bool touchpadRightClickZoneEnabled;
@@ -229,6 +243,9 @@
 
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
+            defaultPointerDisplayId(ui::LogicalDisplayId::DEFAULT),
+            mousePointerSpeed(0),
+            displaysWithMousePointerAccelerationDisabled(),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
                                              static_cast<float>(
                                                      android::os::IInputConstants::
@@ -246,11 +263,11 @@
             pointerGestureSwipeMaxWidthRatio(0.25f),
             pointerGestureMovementSpeedRatio(0.8f),
             pointerGestureZoomSpeedRatio(0.3f),
-            showTouches(false),
             pointerCaptureRequest(),
             touchpadPointerSpeed(0),
             touchpadNaturalScrollingEnabled(true),
             touchpadTapToClickEnabled(true),
+            touchpadTapDraggingEnabled(false),
             touchpadRightClickZoneEnabled(false),
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false) {}
@@ -259,7 +276,7 @@
     std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueDisplayId)
             const;
     std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t physicalPort) const;
-    std::optional<DisplayViewport> getDisplayViewportById(int32_t displayId) const;
+    std::optional<DisplayViewport> getDisplayViewportById(ui::LogicalDisplayId displayId) const;
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports);
 
     void dump(std::string& dump) const;
@@ -293,9 +310,6 @@
     /* Called by the heartbeat to ensures that the reader has not deadlocked. */
     virtual void monitor() = 0;
 
-    /* Returns true if the input device is enabled. */
-    virtual bool isInputDeviceEnabled(int32_t deviceId) = 0;
-
     /* Makes the reader start processing events from the kernel. */
     virtual status_t start() = 0;
 
@@ -350,7 +364,7 @@
     virtual std::vector<InputDeviceSensorInfo> getSensors(int32_t deviceId) = 0;
 
     /* Return true if the device can send input events to the specified display. */
-    virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
+    virtual bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) = 0;
 
     /* Enable sensor in input reader mapper. */
     virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
@@ -377,6 +391,12 @@
 
     /* Sysfs node change reported. Recreate device if required to incorporate the new sysfs nodes */
     virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0;
+
+    /* Get the ID of the InputDevice that was used most recently.
+     *
+     * Returns ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID if no device has been used since boot.
+     */
+    virtual DeviceId getLastUsedInputDeviceId() = 0;
 };
 
 // --- TouchAffineTransformation ---
@@ -426,10 +446,6 @@
     /* Gets the input reader configuration. */
     virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0;
 
-    /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
-    virtual std::shared_ptr<PointerControllerInterface> obtainPointerController(
-            int32_t deviceId) = 0;
-
     /* Notifies the input reader policy that some input devices have changed
      * and provides information about all current input devices.
      */
@@ -459,7 +475,7 @@
      * be used as the range of possible values for pointing devices, like mice and touchpads.
      */
     virtual std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
-            int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
+            ui::LogicalDisplayId associatedDisplayId = ui::LogicalDisplayId::INVALID) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h
index 736b1e0..db417cf 100644
--- a/services/inputflinger/include/NotifyArgs.h
+++ b/services/inputflinger/include/NotifyArgs.h
@@ -61,7 +61,7 @@
 
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
     uint32_t policyFlags;
     int32_t action;
     int32_t flags;
@@ -74,9 +74,9 @@
     inline NotifyKeyArgs() {}
 
     NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
-                  uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action,
-                  int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
-                  nsecs_t downTime);
+                  uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
+                  int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+                  int32_t metaState, nsecs_t downTime);
 
     bool operator==(const NotifyKeyArgs& rhs) const = default;
 
@@ -91,7 +91,7 @@
 
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
     uint32_t policyFlags;
     int32_t action;
     int32_t actionButton;
@@ -123,12 +123,12 @@
     inline NotifyMotionArgs() {}
 
     NotifyMotionArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
-                     uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action,
-                     int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
-                     MotionClassification classification, int32_t edgeFlags, uint32_t pointerCount,
-                     const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
-                     float xPrecision, float yPrecision, float xCursorPosition,
-                     float yCursorPosition, nsecs_t downTime,
+                     uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
+                     int32_t action, int32_t actionButton, int32_t flags, int32_t metaState,
+                     int32_t buttonState, MotionClassification classification, int32_t edgeFlags,
+                     uint32_t pointerCount, const PointerProperties* pointerProperties,
+                     const PointerCoords* pointerCoords, float xPrecision, float yPrecision,
+                     float xCursorPosition, float yCursorPosition, nsecs_t downTime,
                      const std::vector<TouchVideoFrame>& videoFrames);
 
     NotifyMotionArgs(const NotifyMotionArgs& other) = default;
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index e4363a4..cae638f 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -19,7 +19,6 @@
 #include <NotifyArgs.h>
 #include <android/input.h>
 #include <attestation/HmacKeyManager.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <input/InputEventBuilders.h>
 #include <utils/Timers.h> // for nsecs_t, systemTime
@@ -30,8 +29,11 @@
 
 class MotionArgsBuilder {
 public:
-    MotionArgsBuilder(int32_t action, int32_t source) {
+    MotionArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
         mAction = action;
+        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+            addFlag(AMOTION_EVENT_FLAG_CANCELED);
+        }
         mSource = source;
         mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
         mDownTime = mEventTime;
@@ -52,7 +54,7 @@
         return *this;
     }
 
-    MotionArgsBuilder& displayId(int32_t displayId) {
+    MotionArgsBuilder& displayId(ui::LogicalDisplayId displayId) {
         mDisplayId = displayId;
         return *this;
     }
@@ -97,7 +99,7 @@
         return *this;
     }
 
-    NotifyMotionArgs build() {
+    NotifyMotionArgs build() const {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
         for (const PointerBuilder& pointer : mPointers) {
@@ -106,17 +108,17 @@
         }
 
         // Set mouse cursor position for the most common cases to avoid boilerplate.
+        float resolvedCursorX = mRawXCursorPosition;
+        float resolvedCursorY = mRawYCursorPosition;
         if (mSource == AINPUT_SOURCE_MOUSE &&
-            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) {
-            mRawXCursorPosition = pointerCoords[0].getX();
-            mRawYCursorPosition = pointerCoords[0].getY();
+            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
+            BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) &&
+            BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) {
+            resolvedCursorX = pointerCoords[0].getX();
+            resolvedCursorY = pointerCoords[0].getY();
         }
 
-        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
-            addFlag(AMOTION_EVENT_FLAG_CANCELED);
-        }
-
-        return {InputEvent::nextId(),
+        return {mEventId,
                 mEventTime,
                 /*readTime=*/mEventTime,
                 mDeviceId,
@@ -135,19 +137,20 @@
                 pointerCoords.data(),
                 /*xPrecision=*/0,
                 /*yPrecision=*/0,
-                mRawXCursorPosition,
-                mRawYCursorPosition,
+                resolvedCursorX,
+                resolvedCursorY,
                 mDownTime,
                 /*videoFrames=*/{}};
     }
 
 private:
+    const int32_t mEventId;
     int32_t mAction;
     int32_t mDeviceId{DEFAULT_DEVICE_ID};
     uint32_t mSource;
     nsecs_t mDownTime;
     nsecs_t mEventTime;
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
     uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
     int32_t mActionButton{0};
     int32_t mButtonState{0};
@@ -161,7 +164,7 @@
 
 class KeyArgsBuilder {
 public:
-    KeyArgsBuilder(int32_t action, int32_t source) {
+    KeyArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
         mAction = action;
         mSource = source;
         mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -183,7 +186,7 @@
         return *this;
     }
 
-    KeyArgsBuilder& displayId(int32_t displayId) {
+    KeyArgsBuilder& displayId(ui::LogicalDisplayId displayId) {
         mDisplayId = displayId;
         return *this;
     }
@@ -204,7 +207,7 @@
     }
 
     NotifyKeyArgs build() const {
-        return {InputEvent::nextId(),
+        return {mEventId,
                 mEventTime,
                 /*readTime=*/mEventTime,
                 mDeviceId,
@@ -220,12 +223,13 @@
     }
 
 private:
+    const int32_t mEventId;
     int32_t mAction;
     int32_t mDeviceId = DEFAULT_DEVICE_ID;
     uint32_t mSource;
     nsecs_t mDownTime;
     nsecs_t mEventTime;
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
     uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
     int32_t mFlags{0};
     int32_t mKeyCode{AKEYCODE_UNKNOWN};
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index 8b47b55..f6dc109 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -25,6 +25,9 @@
  *
  * This is the interface that PointerChoreographer uses to talk to Window Manager and other
  * system components.
+ *
+ * NOTE: In general, the PointerChoreographer must not interact with the policy while
+ * holding any locks.
  */
 class PointerChoreographerPolicyInterface {
 public:
@@ -37,6 +40,9 @@
      * for and runnable on the host, the PointerController implementation must be in a separate
      * library, libinputservice, that has the additional dependencies. The PointerController
      * will be mocked when testing PointerChoreographer.
+     *
+     * Since this is a factory method used to work around dependencies, it will not interact with
+     * other input components and may be called with the PointerChoreographer lock held.
      */
     virtual std::shared_ptr<PointerControllerInterface> createPointerController(
             PointerControllerInterface::ControllerType type) = 0;
@@ -47,7 +53,8 @@
      * @param displayId The updated display on which the mouse cursor is shown
      * @param position The new position of the mouse cursor on the logical display
      */
-    virtual void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
+    virtual void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
+                                               const FloatPoint& position) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index c44486f..cee44fc 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -61,8 +61,6 @@
      * TODO(b/293587049): Refactor the PointerController class into different controller types.
      */
     enum class ControllerType {
-        // The PointerController that is responsible for drawing all icons.
-        LEGACY,
         // Represents a single mouse pointer.
         MOUSE,
         // Represents multiple touch spots.
@@ -127,13 +125,13 @@
      * pressed (not hovering).
      */
     virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                          BitSet32 spotIdBits, int32_t displayId) = 0;
+                          BitSet32 spotIdBits, ui::LogicalDisplayId displayId) = 0;
 
     /* Removes all spots. */
     virtual void clearSpots() = 0;
 
     /* Gets the id of the display where the pointer should be shown. */
-    virtual int32_t getDisplayId() const = 0;
+    virtual ui::LogicalDisplayId getDisplayId() const = 0;
 
     /* Sets the associated display of this pointer. Pointer should show on that display. */
     virtual void setDisplayViewport(const DisplayViewport& displayViewport) = 0;
@@ -143,6 +141,11 @@
 
     /* Sets the custom pointer icon for mice or styluses. */
     virtual void setCustomPointerIcon(const SpriteIcon& icon) = 0;
+
+    /* Sets the flag to skip screenshot of the pointer indicators on the display matching the
+     * provided displayId.
+     */
+    virtual void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index f954370..ba586d7 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index f7bbc51..fe70a51 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -123,7 +123,8 @@
          {"multi_index", InputLightClass::MULTI_INDEX},
          {"multi_intensity", InputLightClass::MULTI_INTENSITY},
          {"max_brightness", InputLightClass::MAX_BRIGHTNESS},
-         {"kbd_backlight", InputLightClass::KEYBOARD_BACKLIGHT}};
+         {"kbd_backlight", InputLightClass::KEYBOARD_BACKLIGHT},
+         {"mic_mute", InputLightClass::KEYBOARD_MIC_MUTE}};
 
 // Mapping for input multicolor led class node names.
 // https://www.kernel.org/doc/html/latest/leds/leds-class-multicolor.html
@@ -810,14 +811,20 @@
         case EV_SYN: {
             switch (event.code) {
                 case SYN_REPORT:
-                    currentFrameDropped = false;
+                    if (currentFrameDropped) {
+                        // To recover after a SYN_DROPPED, we need to query the state of the device
+                        // to synchronize our device state with the kernel's to account for the
+                        // dropped events on receiving the next SYN_REPORT.
+                        // Note we don't drop the SYN_REPORT at this point but it is used by the
+                        // InputDevice to reset and repopulate mapper state
+                        readDeviceState();
+                        currentFrameDropped = false;
+                    }
                     break;
                 case SYN_DROPPED:
                     // When we receive SYN_DROPPED, all events in the current frame should be
-                    // dropped. We query the state of the device to synchronize our device state
-                    // with the kernel's to account for the dropped events.
+                    // dropped up to and including next SYN_REPORT
                     currentFrameDropped = true;
-                    readDeviceState();
                     break;
                 default:
                     break;
@@ -1141,6 +1148,22 @@
     return OK;
 }
 
+base::Result<std::vector<int32_t>> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                             size_t slotCount) const {
+    std::scoped_lock _l(mLock);
+    const Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr || !device->hasValidFd() || !device->absBitmask.test(axis)) {
+        return base::ResultError("device problem or axis not supported", NAME_NOT_FOUND);
+    }
+    std::vector<int32_t> outValues(slotCount + 1);
+    outValues[0] = axis;
+    const size_t bufferSize = outValues.size() * sizeof(int32_t);
+    if (ioctl(device->fd, EVIOCGMTSLOTS(bufferSize), outValues.data()) != OK) {
+        return base::ErrnoError();
+    }
+    return std::move(outValues);
+}
+
 bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
                                      uint8_t* outFlags) const {
     std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index fb32f96..b807b27 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -77,11 +77,11 @@
 
         // If a device is associated with a specific display but there is no
         // associated DisplayViewport, don't enable the device.
-        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueId) &&
+        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueIdByPort) &&
             !mAssociatedViewport) {
             const std::string desc = mAssociatedDisplayPort
                     ? "port " + std::to_string(*mAssociatedDisplayPort)
-                    : "uniqueId " + *mAssociatedDisplayUniqueId;
+                    : "uniqueId " + *mAssociatedDisplayUniqueIdByPort;
             ALOGW("Cannot enable input device %s because it is associated "
                   "with %s, but the corresponding viewport is not found",
                   getName().c_str(), desc.c_str());
@@ -124,9 +124,15 @@
     } else {
         dump += "<none>\n";
     }
-    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueId: ");
-    if (mAssociatedDisplayUniqueId) {
-        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueId->c_str());
+    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByPort: ");
+    if (mAssociatedDisplayUniqueIdByPort) {
+        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByPort->c_str());
+    } else {
+        dump += "<none>\n";
+    }
+    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByDescriptor: ");
+    if (mAssociatedDisplayUniqueIdByDescriptor) {
+        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByDescriptor->c_str());
     } else {
         dump += "<none>\n";
     }
@@ -251,6 +257,7 @@
             mAssociatedDeviceType =
                     getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location);
             mIsWaking = mConfiguration.getBool("device.wake").value_or(false);
+            mShouldSmoothScroll = mConfiguration.getBool("device.viewBehavior_smoothScroll");
         }
 
         if (!changes.any() || changes.test(Change::DEVICE_ALIAS)) {
@@ -264,24 +271,46 @@
         }
 
         if (!changes.any() || changes.test(Change::DISPLAY_INFO)) {
+            const auto oldAssociatedDisplayId = getAssociatedDisplayId();
+
             // In most situations, no port or name will be specified.
             mAssociatedDisplayPort = std::nullopt;
-            mAssociatedDisplayUniqueId = std::nullopt;
+            mAssociatedDisplayUniqueIdByPort = std::nullopt;
             mAssociatedViewport = std::nullopt;
+            // Find the display port that corresponds to the current input device descriptor
+            const std::string& inputDeviceDescriptor = mIdentifier.descriptor;
+            if (!inputDeviceDescriptor.empty()) {
+                const std::unordered_map<std::string, uint8_t>& ports =
+                        readerConfig.inputPortToDisplayPortAssociations;
+                const auto& displayPort = ports.find(inputDeviceDescriptor);
+                if (displayPort != ports.end()) {
+                    mAssociatedDisplayPort = std::make_optional(displayPort->second);
+                } else {
+                    const std::unordered_map<std::string, std::string>&
+                            displayUniqueIdsByDescriptor =
+                                    readerConfig.inputDeviceDescriptorToDisplayUniqueIdAssociations;
+                    const auto& displayUniqueIdByDescriptor =
+                            displayUniqueIdsByDescriptor.find(inputDeviceDescriptor);
+                    if (displayUniqueIdByDescriptor != displayUniqueIdsByDescriptor.end()) {
+                        mAssociatedDisplayUniqueIdByDescriptor =
+                                displayUniqueIdByDescriptor->second;
+                    }
+                }
+            }
             // Find the display port that corresponds to the current input port.
             const std::string& inputPort = mIdentifier.location;
             if (!inputPort.empty()) {
                 const std::unordered_map<std::string, uint8_t>& ports =
-                        readerConfig.portAssociations;
+                        readerConfig.inputPortToDisplayPortAssociations;
                 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 =
-                            readerConfig.uniqueIdAssociations;
-                    const auto& displayUniqueId = displayUniqueIds.find(inputPort);
-                    if (displayUniqueId != displayUniqueIds.end()) {
-                        mAssociatedDisplayUniqueId = displayUniqueId->second;
+                    const std::unordered_map<std::string, std::string>& displayUniqueIdsByPort =
+                            readerConfig.inputPortToDisplayUniqueIdAssociations;
+                    const auto& displayUniqueIdByPort = displayUniqueIdsByPort.find(inputPort);
+                    if (displayUniqueIdByPort != displayUniqueIdsByPort.end()) {
+                        mAssociatedDisplayUniqueIdByPort = displayUniqueIdByPort->second;
                     }
                 }
             }
@@ -296,14 +325,26 @@
                           "but the corresponding viewport is not found.",
                           getName().c_str(), *mAssociatedDisplayPort);
                 }
-            } else if (mAssociatedDisplayUniqueId != std::nullopt) {
-                mAssociatedViewport =
-                        readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
+            } else if (mAssociatedDisplayUniqueIdByDescriptor != std::nullopt) {
+                mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+                        *mAssociatedDisplayUniqueIdByDescriptor);
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
-                          getName().c_str(), mAssociatedDisplayUniqueId->c_str());
+                          getName().c_str(), mAssociatedDisplayUniqueIdByDescriptor->c_str());
                 }
+            } else if (mAssociatedDisplayUniqueIdByPort != std::nullopt) {
+                mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+                        *mAssociatedDisplayUniqueIdByPort);
+                if (!mAssociatedViewport) {
+                    ALOGW("Input device %s should be associated with display %s but the "
+                          "corresponding viewport cannot be found",
+                          getName().c_str(), mAssociatedDisplayUniqueIdByPort->c_str());
+                }
+            }
+
+            if (getAssociatedDisplayId() != oldAssociatedDisplayId) {
+                bumpGeneration();
             }
         }
 
@@ -350,6 +391,7 @@
 
         if (mDropUntilNextSync) {
             if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+                out += reset(rawEvent->when);
                 mDropUntilNextSync = false;
                 ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun.");
             } else {
@@ -359,7 +401,6 @@
         } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
             ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
             mDropUntilNextSync = true;
-            out += reset(rawEvent->when);
         } else {
             for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {
                 out += mapper.process(rawEvent);
@@ -401,7 +442,9 @@
 InputDeviceInfo InputDevice::getDeviceInfo() {
     InputDeviceInfo outDeviceInfo;
     outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
-                             mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE));
+                             mHasMic,
+                             getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID),
+                             {mShouldSmoothScroll}, isEnabled());
 
     for_each_mapper(
             [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); });
@@ -499,15 +542,7 @@
     }
 
     // Touchscreens and touchpad devices.
-    static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
-            sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
-    // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or
-    // at least load this setting from the IDC file.
-    const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier();
-    const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c &&
-            (identifier.product == 0x05c4 || identifier.product == 0x09cc);
-    if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
-        classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) {
+    if (classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
     } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(createInputMapper<MultiTouchInputMapper>(contextPtr, readerConfig));
@@ -665,14 +700,14 @@
     return NotifyDeviceResetArgs(mContext->getNextId(), when, mId);
 }
 
-std::optional<int32_t> InputDevice::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> InputDevice::getAssociatedDisplayId() {
     // Check if we had associated to the specific display.
     if (mAssociatedViewport) {
         return mAssociatedViewport->displayId;
     }
 
     // No associated display port, check if some InputMapper is associated.
-    return first_in_mappers<int32_t>(
+    return first_in_mappers<ui::LogicalDisplayId>(
             [](InputMapper& mapper) { return mapper.getAssociatedDisplayId(); });
 }
 
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 0582649..b9523ef 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -38,6 +38,8 @@
 
 namespace android {
 
+namespace {
+
 /**
  * Determines if the identifiers passed are a sub-devices. Sub-devices are physical devices
  * that expose multiple input device paths such a keyboard that also has a touchpad input.
@@ -49,8 +51,8 @@
  *    inputs versus the same device plugged into multiple ports.
  */
 
-static bool isSubDevice(const InputDeviceIdentifier& identifier1,
-                        const InputDeviceIdentifier& identifier2) {
+bool isSubDevice(const InputDeviceIdentifier& identifier1,
+                 const InputDeviceIdentifier& identifier2) {
     return (identifier1.vendor == identifier2.vendor &&
             identifier1.product == identifier2.product && identifier1.bus == identifier2.bus &&
             identifier1.version == identifier2.version &&
@@ -58,7 +60,7 @@
             identifier1.location == identifier2.location);
 }
 
-static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) {
+bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) {
     const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action);
     if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER &&
         actionMasked != AMOTION_EVENT_ACTION_DOWN &&
@@ -69,6 +71,28 @@
     return isStylusToolType(motionArgs.pointerProperties[actionIndex].toolType);
 }
 
+bool isNewGestureStart(const NotifyMotionArgs& motion) {
+    return motion.action == AMOTION_EVENT_ACTION_DOWN ||
+            motion.action == AMOTION_EVENT_ACTION_HOVER_ENTER;
+}
+
+bool isNewGestureStart(const NotifyKeyArgs& key) {
+    return key.action == AKEY_EVENT_ACTION_DOWN;
+}
+
+// Return the event's device ID if it marks the start of a new gesture.
+std::optional<DeviceId> getDeviceIdOfNewGesture(const NotifyArgs& args) {
+    if (const auto* motion = std::get_if<NotifyMotionArgs>(&args); motion != nullptr) {
+        return isNewGestureStart(*motion) ? std::make_optional(motion->deviceId) : std::nullopt;
+    }
+    if (const auto* key = std::get_if<NotifyKeyArgs>(&args); key != nullptr) {
+        return isNewGestureStart(*key) ? std::make_optional(key->deviceId) : std::nullopt;
+    }
+    return std::nullopt;
+}
+
+} // namespace
+
 // --- InputReader ---
 
 InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
@@ -162,20 +186,12 @@
         }
 
         std::swap(notifyArgs, mPendingArgs);
-    } // release lock
 
-    // Send out a message that the describes the changed input devices.
-    if (inputDevicesChanged) {
-        mPolicy->notifyInputDevicesChanged(inputDevices);
-    }
-
-    // Notify the policy of the start of every new stylus gesture outside the lock.
-    for (const auto& args : notifyArgs) {
-        const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args);
-        if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
-            mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
+        // Keep track of the last used device
+        for (const NotifyArgs& args : notifyArgs) {
+            mLastUsedDeviceId = getDeviceIdOfNewGesture(args).value_or(mLastUsedDeviceId);
         }
-    }
+    } // release lock
 
     // Flush queued events out to the listener.
     // This must happen outside of the lock because the listener could potentially call
@@ -187,6 +203,21 @@
     for (const NotifyArgs& args : notifyArgs) {
         mNextListener.notify(args);
     }
+
+    // Notify the policy that input devices have changed.
+    // This must be done after flushing events down the listener chain to ensure that the rest of
+    // the listeners are synchronized with the changes before the policy reacts to them.
+    if (inputDevicesChanged) {
+        mPolicy->notifyInputDevicesChanged(inputDevices);
+    }
+
+    // Notify the policy of the start of every new stylus gesture.
+    for (const auto& args : notifyArgs) {
+        const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args);
+        if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
+            mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
+        }
+    }
 }
 
 std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
@@ -400,10 +431,6 @@
     ALOGI("Reconfiguring input devices, changes=%s", changes.string().c_str());
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
 
-    if (changes.test(Change::DISPLAY_INFO)) {
-        updatePointerDisplayLocked();
-    }
-
     if (changes.test(Change::MUST_REOPEN)) {
         mEventHub->requestReopenDevices();
     } else {
@@ -488,47 +515,6 @@
     }
 }
 
-std::shared_ptr<PointerControllerInterface> InputReader::getPointerControllerLocked(
-        int32_t deviceId) {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller == nullptr) {
-        controller = mPolicy->obtainPointerController(deviceId);
-        mPointerController = controller;
-        updatePointerDisplayLocked();
-    }
-    return controller;
-}
-
-void InputReader::updatePointerDisplayLocked() {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller == nullptr) {
-        return;
-    }
-
-    std::optional<DisplayViewport> viewport =
-            mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
-    if (!viewport) {
-        ALOGW("Can't find the designated viewport with ID %" PRId32 " to update cursor input "
-              "mapper. Fall back to default display",
-              mConfig.defaultPointerDisplayId);
-        viewport = mConfig.getDisplayViewportById(ADISPLAY_ID_DEFAULT);
-    }
-    if (!viewport) {
-        ALOGE("Still can't find a viable viewport to update cursor input mapper. Skip setting it to"
-              " PointerController.");
-        return;
-    }
-
-    controller->setDisplayViewport(*viewport);
-}
-
-void InputReader::fadePointerLocked() {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller != nullptr) {
-        controller->fade(PointerControllerInterface::Transition::GRADUAL);
-    }
-}
-
 void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
     if (when < mNextTimeout) {
         mNextTimeout = when;
@@ -888,18 +874,7 @@
     return std::nullopt;
 }
 
-bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
-    std::scoped_lock _l(mLock);
-
-    InputDevice* device = findInputDeviceLocked(deviceId);
-    if (device) {
-        return device->isEnabled();
-    }
-    ALOGW("Ignoring invalid device id %" PRId32 ".", deviceId);
-    return false;
-}
-
-bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) {
+bool InputReader::canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) {
     std::scoped_lock _l(mLock);
 
     InputDevice* device = findInputDeviceLocked(deviceId);
@@ -913,10 +888,9 @@
         return false;
     }
 
-    std::optional<int32_t> associatedDisplayId = device->getAssociatedDisplayId();
+    std::optional<ui::LogicalDisplayId> associatedDisplayId = device->getAssociatedDisplayId();
     // No associated display. By default, can dispatch to all displays.
-    if (!associatedDisplayId ||
-            *associatedDisplayId == ADISPLAY_ID_NONE) {
+    if (!associatedDisplayId || !associatedDisplayId->isValid()) {
         return true;
     }
 
@@ -927,6 +901,11 @@
     mEventHub->sysfsNodeChanged(sysfsNodePath);
 }
 
+DeviceId InputReader::getLastUsedInputDeviceId() {
+    std::scoped_lock _l(mLock);
+    return mLastUsedDeviceId;
+}
+
 void InputReader::dump(std::string& dump) {
     std::scoped_lock _l(mLock);
 
@@ -1065,17 +1044,6 @@
     return mReader->shouldDropVirtualKeyLocked(now, keyCode, scanCode);
 }
 
-void InputReader::ContextImpl::fadePointer() {
-    // lock is already held by the input loop
-    mReader->fadePointerLocked();
-}
-
-std::shared_ptr<PointerControllerInterface> InputReader::ContextImpl::getPointerController(
-        int32_t deviceId) {
-    // lock is already held by the input loop
-    return mReader->getPointerControllerLocked(deviceId);
-}
-
 void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) {
     // lock is already held by the input loop
     mReader->requestTimeoutAtTimeLocked(when);
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index eabf591..27b9d23 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -505,9 +505,14 @@
 
     // Check the rest of raw light infos
     for (const auto& [rawId, rawInfo] : rawInfos) {
-        InputDeviceLightType type = keyboardBacklightIds.find(rawId) != keyboardBacklightIds.end()
-                ? InputDeviceLightType::KEYBOARD_BACKLIGHT
-                : InputDeviceLightType::INPUT;
+        InputDeviceLightType type;
+        if (keyboardBacklightIds.find(rawId) != keyboardBacklightIds.end()) {
+            type = InputDeviceLightType::KEYBOARD_BACKLIGHT;
+        } else if (rawInfo.flags.test(InputLightClass::KEYBOARD_MIC_MUTE)) {
+            type = InputDeviceLightType::KEYBOARD_MIC_MUTE;
+        } else {
+            type = InputDeviceLightType::INPUT;
+        }
 
         // If the node is multi-color led, construct a MULTI_COLOR light
         if (rawInfo.flags.test(InputLightClass::MULTI_INDEX) &&
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 0bcab42..39d2f5b 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -177,6 +177,8 @@
     MAX_BRIGHTNESS = 0x00000080,
     /* The input light has kbd_backlight name */
     KEYBOARD_BACKLIGHT = 0x00000100,
+    /* The input light has mic_mute name */
+    KEYBOARD_MIC_MUTE = 0x00000200,
 };
 
 enum class InputBatteryClass : uint32_t {
@@ -336,6 +338,10 @@
     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;
+    /* Query Multi-Touch slot values for an axis. Returns error or an 1 indexed array of size
+     * (slotCount + 1). The value at the 0 index is set to queried axis. */
+    virtual base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                               size_t slotCount) const = 0;
     virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
 
     /*
@@ -552,6 +558,8 @@
                                      int32_t locationKeyCode) const override final;
     status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
                                   int32_t* outValue) const override final;
+    base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                       size_t slotCount) const override final;
 
     bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
                                uint8_t* outFlags) const override final;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 31dcb2e..4c9af2e 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -63,8 +63,11 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mAssociatedDisplayPort;
     }
-    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
-        return mAssociatedDisplayUniqueId;
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+        return mAssociatedDisplayUniqueIdByPort;
+    }
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+        return mAssociatedDisplayUniqueIdByDescriptor;
     }
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mAssociatedDeviceType;
@@ -128,7 +131,7 @@
     inline const PropertyMap& getConfiguration() { return mConfiguration; }
     inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
 
-    std::optional<int32_t> getAssociatedDisplayId();
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId();
 
     void updateLedState(bool reset);
 
@@ -194,11 +197,13 @@
     bool mIsWaking;
     bool mIsExternal;
     std::optional<uint8_t> mAssociatedDisplayPort;
-    std::optional<std::string> mAssociatedDisplayUniqueId;
+    std::optional<std::string> mAssociatedDisplayUniqueIdByPort;
+    std::optional<std::string> mAssociatedDisplayUniqueIdByDescriptor;
     std::optional<std::string> mAssociatedDeviceType;
     std::optional<DisplayViewport> mAssociatedViewport;
     bool mHasMic;
     bool mDropUntilNextSync;
+    std::optional<bool> mShouldSmoothScroll;
 
     typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
     int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
@@ -280,7 +285,7 @@
 class InputDeviceContext {
 public:
     InputDeviceContext(InputDevice& device, int32_t eventHubId);
-    ~InputDeviceContext();
+    virtual ~InputDeviceContext();
 
     inline InputReaderContext* getContext() { return mContext; }
     inline int32_t getId() { return mDeviceId; }
@@ -372,6 +377,10 @@
     inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const {
         return mEventHub->getAbsoluteAxisValue(mId, code, outValue);
     }
+    inline base::Result<std::vector<int32_t>> getMtSlotValues(int32_t axis,
+                                                              size_t slotCount) const {
+        return mEventHub->getMtSlotValues(mId, axis, slotCount);
+    }
     inline bool markSupportedKeyCodes(const std::vector<int32_t>& keyCodes,
                                       uint8_t* outFlags) const {
         return mEventHub->markSupportedKeyCodes(mId, keyCodes, outFlags);
@@ -444,13 +453,16 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
     }
-    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
-        return mDevice.getAssociatedDisplayUniqueId();
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+        return mDevice.getAssociatedDisplayUniqueIdByPort();
+    }
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+        return mDevice.getAssociatedDisplayUniqueIdByDescriptor();
     }
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mDevice.getDeviceTypeAssociation();
     }
-    inline std::optional<DisplayViewport> getAssociatedViewport() const {
+    virtual std::optional<DisplayViewport> getAssociatedViewport() const {
         return mDevice.getAssociatedViewport();
     }
     [[nodiscard]] inline std::list<NotifyArgs> cancelTouch(nsecs_t when, nsecs_t readTime) {
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 4c78db3..7e701c5 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <PointerControllerInterface.h>
 #include <android-base/thread_annotations.h>
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
@@ -62,8 +61,6 @@
 
     std::vector<InputDeviceInfo> getInputDevices() const override;
 
-    bool isInputDeviceEnabled(int32_t deviceId) override;
-
     int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) override;
     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;
@@ -87,7 +84,7 @@
 
     std::vector<int32_t> getVibratorIds(int32_t deviceId) override;
 
-    bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) override;
+    bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) override;
 
     bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
                       std::chrono::microseconds samplingPeriod,
@@ -119,6 +116,8 @@
 
     void sysfsNodeChanged(const std::string& sysfsNodePath) override;
 
+    DeviceId getLastUsedInputDeviceId() override;
+
 protected:
     // These members are protected so they can be instrumented by test cases.
     virtual std::shared_ptr<InputDevice> createDeviceLocked(nsecs_t when, int32_t deviceId,
@@ -141,9 +140,6 @@
         void disableVirtualKeysUntil(nsecs_t time) REQUIRES(mReader->mLock) override;
         bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode)
                 REQUIRES(mReader->mLock) override;
-        void fadePointer() REQUIRES(mReader->mLock) override;
-        std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId)
-                REQUIRES(mReader->mLock) override;
         void requestTimeoutAtTime(nsecs_t when) REQUIRES(mReader->mLock) override;
         int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override;
         void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices)
@@ -204,6 +200,9 @@
     // records timestamp of the last key press on the physical keyboard
     nsecs_t mLastKeyDownTimestamp GUARDED_BY(mLock){0};
 
+    // The input device that produced a new gesture most recently.
+    DeviceId mLastUsedDeviceId GUARDED_BY(mLock){ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID};
+
     // low-level input event decoding and device management
     [[nodiscard]] std::list<NotifyArgs> processEventsLocked(const RawEvent* rawEvents, size_t count)
             REQUIRES(mLock);
@@ -230,13 +229,6 @@
     [[nodiscard]] std::list<NotifyArgs> dispatchExternalStylusStateLocked(const StylusState& state)
             REQUIRES(mLock);
 
-    // The PointerController that is shared among all the input devices that need it.
-    std::weak_ptr<PointerControllerInterface> mPointerController;
-    std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId)
-            REQUIRES(mLock);
-    void updatePointerDisplayLocked() REQUIRES(mLock);
-    void fadePointerLocked() REQUIRES(mLock);
-
     int32_t mGeneration GUARDED_BY(mLock);
     int32_t bumpGenerationLocked() REQUIRES(mLock);
 
diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h
index 69b2315..907a49f 100644
--- a/services/inputflinger/reader/include/InputReaderContext.h
+++ b/services/inputflinger/reader/include/InputReaderContext.h
@@ -28,7 +28,6 @@
 class InputListenerInterface;
 class InputMapper;
 class InputReaderPolicyInterface;
-class PointerControllerInterface;
 struct StylusState;
 
 /* Internal interface used by individual input devices to access global input device state
@@ -45,9 +44,6 @@
     virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
     virtual bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) = 0;
 
-    virtual void fadePointer() = 0;
-    virtual std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) = 0;
-
     virtual void requestTimeoutAtTime(nsecs_t when) = 0;
     virtual int32_t bumpGeneration() = 0;
 
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
index 061c6a3..09a5f08 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -19,7 +19,6 @@
 #include <sstream>
 
 #include <android-base/stringprintf.h>
-#include <gui/constants.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
@@ -279,7 +278,7 @@
     LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(),
                         "Mismatched coords and properties arrays.");
     return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
-                            ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action,
+                            ui::LogicalDisplayId::INVALID, /*policyFlags=*/POLICY_FLAG_WAKE, action,
                             /*actionButton=*/actionButton, flags,
                             mReaderContext.getGlobalMetaState(), mButtonState,
                             MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(),
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 58e35a6..c67314d 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -20,9 +20,12 @@
 
 #include "CursorInputMapper.h"
 
-#include <com_android_input_flags.h>
 #include <optional>
 
+#include <com_android_input_flags.h>
+#include <ftl/enum.h>
+#include <input/AccelerationCurve.h>
+
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
 #include "PointerControllerInterface.h"
@@ -75,13 +78,7 @@
                                      const InputReaderConfiguration& readerConfig)
       : InputMapper(deviceContext, readerConfig),
         mLastEventTime(std::numeric_limits<nsecs_t>::min()),
-        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {}
-
-CursorInputMapper::~CursorInputMapper() {
-    if (mPointerController != nullptr) {
-        mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-    }
-}
+        mEnableNewMousePointerBallistics(input_flags::enable_new_mouse_pointer_ballistics()) {}
 
 uint32_t CursorInputMapper::getSources() const {
     return mSource;
@@ -132,8 +129,9 @@
                          mWheelXVelocityControl.getParameters().dump().c_str());
     dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
     dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
-    dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
-    dump += StringPrintf(INDENT3 "Orientation: %d\n", mOrientation);
+    dump += StringPrintf(INDENT3 "DisplayId: %s\n",
+                         toString(mDisplayId, streamableToString).c_str());
+    dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(mOrientation).c_str());
     dump += StringPrintf(INDENT3 "ButtonState: 0x%08x\n", mButtonState);
     dump += StringPrintf(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState)));
     dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime);
@@ -156,15 +154,16 @@
         out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
     }
 
-    if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) ||
-        configurePointerCapture) {
-        configureOnChangePointerSpeed(readerConfig);
-    }
-
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) ||
         configurePointerCapture) {
         configureOnChangeDisplayInfo(readerConfig);
     }
+
+    // Pointer speed settings depend on display settings.
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) ||
+        changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) {
+        configureOnChangePointerSpeed(readerConfig);
+    }
     return out;
 }
 
@@ -204,7 +203,8 @@
     mDownTime = 0;
     mLastEventTime = std::numeric_limits<nsecs_t>::min();
 
-    mPointerVelocityControl.reset();
+    mOldPointerVelocityControl.reset();
+    mNewPointerVelocityControl.reset();
     mWheelXVelocityControl.reset();
     mWheelYVelocityControl.reset();
 
@@ -282,27 +282,15 @@
     mWheelYVelocityControl.move(when, nullptr, &vscroll);
     mWheelXVelocityControl.move(when, &hscroll, nullptr);
 
-    mPointerVelocityControl.move(when, &deltaX, &deltaY);
+    if (mEnableNewMousePointerBallistics) {
+        mNewPointerVelocityControl.move(when, &deltaX, &deltaY);
+    } else {
+        mOldPointerVelocityControl.move(when, &deltaX, &deltaY);
+    }
 
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mSource == AINPUT_SOURCE_MOUSE) {
-        if (!mEnablePointerChoreographer) {
-            if (moved || scrolled || buttonsChanged) {
-                mPointerController->setPresentation(
-                        PointerControllerInterface::Presentation::POINTER);
-
-                if (moved) {
-                    mPointerController->move(deltaX, deltaY);
-                }
-                mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-            }
-
-            std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
-
-            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
-        }
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
     } else {
@@ -430,7 +418,7 @@
     }
 }
 
-std::optional<int32_t> CursorInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> CursorInputMapper::getAssociatedDisplayId() {
     return mDisplayId;
 }
 
@@ -453,7 +441,6 @@
             mYPrecision = 1.0f;
             mXScale = 1.0f;
             mYScale = 1.0f;
-            mPointerController = getContext()->getPointerController(getDeviceId());
             break;
         case Parameters::Mode::NAVIGATION:
             mSource = AINPUT_SOURCE_TRACKBALL;
@@ -469,12 +456,10 @@
 }
 
 void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration& config) {
-    if (config.pointerCaptureRequest.enable) {
+    if (config.pointerCaptureRequest.isEnable()) {
         if (mParameters.mode == Parameters::Mode::POINTER) {
             mParameters.mode = Parameters::Mode::POINTER_RELATIVE;
             mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
-            // Keep PointerController around in order to preserve the pointer position.
-            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
         } else {
             ALOGE("Cannot request pointer capture, device is not in MODE_POINTER");
         }
@@ -492,11 +477,27 @@
 void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfiguration& config) {
     if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
         // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled.
-        mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+        if (mEnableNewMousePointerBallistics) {
+            mNewPointerVelocityControl.setAccelerationEnabled(false);
+        } else {
+            mOldPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+        }
         mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
         mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
     } else {
-        mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters);
+        if (mEnableNewMousePointerBallistics) {
+            mNewPointerVelocityControl.setAccelerationEnabled(
+                    config.displaysWithMousePointerAccelerationDisabled.count(
+                            mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0);
+            mNewPointerVelocityControl.setCurve(
+                    createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed));
+        } else {
+            mOldPointerVelocityControl.setParameters(
+                    (config.displaysWithMousePointerAccelerationDisabled.count(
+                             mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0)
+                            ? config.pointerVelocityControlParameters
+                            : FLAT_VELOCITY_CONTROL_PARAMS);
+        }
         mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters);
         mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters);
     }
@@ -505,40 +506,20 @@
 void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfiguration& config) {
     const bool isPointer = mParameters.mode == Parameters::Mode::POINTER;
 
-    mDisplayId = ADISPLAY_ID_NONE;
+    mDisplayId = ui::LogicalDisplayId::INVALID;
     std::optional<DisplayViewport> resolvedViewport;
-    bool isBoundsSet = false;
     if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
         // This InputDevice is associated with a viewport.
         // Only generate events for the associated display.
         mDisplayId = assocViewport->displayId;
         resolvedViewport = *assocViewport;
-        if (!mEnablePointerChoreographer) {
-            const bool mismatchedPointerDisplay =
-                    isPointer && (assocViewport->displayId != mPointerController->getDisplayId());
-            if (mismatchedPointerDisplay) {
-                // This device's associated display doesn't match PointerController's current
-                // display. Do not associate it with any display.
-                mDisplayId.reset();
-            }
-        }
     } else if (isPointer) {
         // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
-        if (mEnablePointerChoreographer) {
-            // Always use DISPLAY_ID_NONE for mouse events.
-            // PointerChoreographer will make it target the correct the displayId later.
-            resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
-            mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
-        } else {
-            mDisplayId = mPointerController->getDisplayId();
-            if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
-                resolvedViewport = *v;
-            }
-            if (auto bounds = mPointerController->getBounds(); bounds) {
-                mBoundsInLogicalDisplay = *bounds;
-                isBoundsSet = true;
-            }
-        }
+        // Always use DISPLAY_ID_NONE for mouse events.
+        // PointerChoreographer will make it target the correct the displayId later.
+        resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+        mDisplayId =
+                resolvedViewport ? std::make_optional(ui::LogicalDisplayId::INVALID) : std::nullopt;
     }
 
     mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) ||
@@ -546,14 +527,12 @@
             ? ui::ROTATION_0
             : getInverseRotation(resolvedViewport->orientation);
 
-    if (!isBoundsSet) {
-        mBoundsInLogicalDisplay = resolvedViewport
-                ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
-                            static_cast<float>(resolvedViewport->logicalTop),
-                            static_cast<float>(resolvedViewport->logicalRight - 1),
-                            static_cast<float>(resolvedViewport->logicalBottom - 1)}
-                : FloatRect{0, 0, 0, 0};
-    }
+    mBoundsInLogicalDisplay = resolvedViewport
+            ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
+                        static_cast<float>(resolvedViewport->logicalTop),
+                        static_cast<float>(resolvedViewport->logicalRight - 1),
+                        static_cast<float>(resolvedViewport->logicalBottom - 1)}
+            : FloatRect{0, 0, 0, 0};
 
     bumpGeneration();
 }
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 308adaa..75ca9c0 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -20,15 +20,11 @@
 #include "CursorScrollAccumulator.h"
 #include "InputMapper.h"
 
-#include <PointerControllerInterface.h>
 #include <input/VelocityControl.h>
 #include <ui/Rotation.h>
 
 namespace android {
 
-class VelocityControl;
-class PointerControllerInterface;
-
 class CursorButtonAccumulator;
 class CursorScrollAccumulator;
 
@@ -57,7 +53,7 @@
     friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
                                                 const InputReaderConfiguration& readerConfig,
                                                 Args... args);
-    virtual ~CursorInputMapper();
+    virtual ~CursorInputMapper() = default;
 
     virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
@@ -70,7 +66,7 @@
 
     virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
 
-    virtual std::optional<int32_t> getAssociatedDisplayId() override;
+    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
 
 private:
     // Amount that trackball needs to move in order to generate a key event.
@@ -111,24 +107,23 @@
 
     // Velocity controls for mouse pointer and wheel movements.
     // The controls for X and Y wheel movements are separate to keep them decoupled.
-    VelocityControl mPointerVelocityControl;
-    VelocityControl mWheelXVelocityControl;
-    VelocityControl mWheelYVelocityControl;
+    SimpleVelocityControl mOldPointerVelocityControl;
+    CurvedVelocityControl mNewPointerVelocityControl;
+    SimpleVelocityControl mWheelXVelocityControl;
+    SimpleVelocityControl mWheelYVelocityControl;
 
     // The display that events generated by this mapper should target. This can be set to
-    // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
+    // LogicalDisplayId::INVALID to target the focused display. If there is no display target (i.e.
     // std::nullopt), all events will be ignored.
-    std::optional<int32_t> mDisplayId;
+    std::optional<ui::LogicalDisplayId> mDisplayId;
     ui::Rotation mOrientation{ui::ROTATION_0};
     FloatRect mBoundsInLogicalDisplay{};
 
-    std::shared_ptr<PointerControllerInterface> mPointerController;
-
     int32_t mButtonState;
     nsecs_t mDownTime;
     nsecs_t mLastEventTime;
 
-    const bool mEnablePointerChoreographer;
+    const bool mEnableNewMousePointerBallistics;
 
     explicit CursorInputMapper(InputDeviceContext& deviceContext,
                                const InputReaderConfiguration& readerConfig);
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 0692dbb..b6c5c98 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -20,6 +20,8 @@
 
 #include <sstream>
 
+#include <ftl/enum.h>
+
 #include "InputDevice.h"
 #include "input/PrintTools.h"
 
@@ -133,7 +135,7 @@
     dump += StringPrintf(INDENT4 "When: %" PRId64 "\n", state.when);
     dump += StringPrintf(INDENT4 "Pressure: %s\n", toString(state.pressure).c_str());
     dump += StringPrintf(INDENT4 "Button State: 0x%08x\n", state.buttons);
-    dump += StringPrintf(INDENT4 "Tool Type: %" PRId32 "\n", state.toolType);
+    dump += StringPrintf(INDENT4 "Tool Type: %s\n", ftl::enum_string(state.toolType).c_str());
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 06de4c2..c7eea0e 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -117,7 +117,7 @@
 
     [[nodiscard]] virtual std::list<NotifyArgs> updateExternalStylusState(const StylusState& state);
 
-    virtual std::optional<int32_t> getAssociatedDisplayId() { return std::nullopt; }
+    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() { return std::nullopt; }
     virtual void updateLedState(bool reset) {}
 
 protected:
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 8a9ea75..5ce4d30 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -341,7 +341,7 @@
     // button will likely wake the device.
     // TODO: Use the input device configuration to control this behavior more finely.
     uint32_t policyFlags = 0;
-    int32_t displayId = ADISPLAY_ID_NONE;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
     if (getDeviceContext().getAssociatedViewport()) {
         displayId = getDeviceContext().getAssociatedViewport()->displayId;
     }
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index f068cc8..2124555 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -20,6 +20,7 @@
 
 #include "KeyboardInputMapper.h"
 
+#include <ftl/enum.h>
 #include <ui/Rotation.h>
 
 namespace android {
@@ -61,6 +62,36 @@
             scanCode >= BTN_WHEEL;
 }
 
+static bool isMediaKey(int32_t keyCode) {
+    switch (keyCode) {
+        case AKEYCODE_MEDIA_PLAY:
+        case AKEYCODE_MEDIA_PAUSE:
+        case AKEYCODE_MEDIA_PLAY_PAUSE:
+        case AKEYCODE_MUTE:
+        case AKEYCODE_HEADSETHOOK:
+        case AKEYCODE_MEDIA_STOP:
+        case AKEYCODE_MEDIA_NEXT:
+        case AKEYCODE_MEDIA_PREVIOUS:
+        case AKEYCODE_MEDIA_REWIND:
+        case AKEYCODE_MEDIA_RECORD:
+        case AKEYCODE_MEDIA_FAST_FORWARD:
+        case AKEYCODE_MEDIA_SKIP_FORWARD:
+        case AKEYCODE_MEDIA_SKIP_BACKWARD:
+        case AKEYCODE_MEDIA_STEP_FORWARD:
+        case AKEYCODE_MEDIA_STEP_BACKWARD:
+        case AKEYCODE_MEDIA_AUDIO_TRACK:
+        case AKEYCODE_VOLUME_UP:
+        case AKEYCODE_VOLUME_DOWN:
+        case AKEYCODE_VOLUME_MUTE:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN:
+            return true;
+        default:
+            return false;
+    }
+}
+
 // --- KeyboardInputMapper ---
 
 KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext,
@@ -79,11 +110,11 @@
     return ui::ROTATION_0;
 }
 
-int32_t KeyboardInputMapper::getDisplayId() {
+ui::LogicalDisplayId KeyboardInputMapper::getDisplayId() {
     if (mViewport) {
         return mViewport->displayId;
     }
-    return ADISPLAY_ID_NONE;
+    return ui::LogicalDisplayId::INVALID;
 }
 
 std::optional<KeyboardLayoutInfo> KeyboardInputMapper::getKeyboardLayoutInfo() const {
@@ -113,7 +144,7 @@
     dump += INDENT2 "Keyboard Input Mapper:\n";
     dumpParameters(dump);
     dump += StringPrintf(INDENT3 "KeyboardType: %d\n", mKeyboardType);
-    dump += StringPrintf(INDENT3 "Orientation: %d\n", getOrientation());
+    dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(getOrientation()).c_str());
     dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size());
     dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState);
     dump += INDENT3 "KeyboardLayoutInfo: ";
@@ -301,7 +332,8 @@
     // For internal keyboards and devices for which the default wake behavior is explicitly
     // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each
     // wake key individually.
-    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault) {
+    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault &&
+        !(mKeyboardType != AINPUT_KEYBOARD_TYPE_ALPHABETIC && isMediaKey(keyCode))) {
         policyFlags |= POLICY_FLAG_WAKE;
     }
 
@@ -425,7 +457,7 @@
     }
 }
 
-std::optional<int32_t> KeyboardInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> KeyboardInputMapper::getAssociatedDisplayId() {
     if (mViewport) {
         return std::make_optional(mViewport->displayId);
     }
@@ -451,16 +483,11 @@
 void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) {
     InputReaderContext& context = *getContext();
     context.setLastKeyDownTimestamp(downTime);
-    if (context.isPreventingTouchpadTaps()) {
-        // avoid pinging java service unnecessarily, just fade pointer again if it became visible
-        context.fadePointer();
-        return;
-    }
+    // TODO(b/338652288): Move cursor fading logic into PointerChoreographer.
     // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
     // shortcuts
     bool shouldHideCursor = mKeyDowns.size() == 1 && !isMetaKey(mKeyDowns[0].keyCode);
     if (shouldHideCursor && context.getPolicy()->isInputMethodConnectionActive()) {
-        context.fadePointer();
         context.setPreventingTouchpadTaps(true);
     }
 }
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 500256b..f2d3f4d 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -46,7 +46,7 @@
 
     int32_t getMetaState() override;
     bool updateMetaState(int32_t keyCode) override;
-    std::optional<int32_t> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
     void updateLedState(bool reset) override;
 
 private:
@@ -91,7 +91,7 @@
     void dumpParameters(std::string& dump) const;
 
     ui::Rotation getOrientation();
-    int32_t getDisplayId();
+    ui::LogicalDisplayId getDisplayId();
 
     [[nodiscard]] std::list<NotifyArgs> processKey(nsecs_t when, nsecs_t readTime, bool down,
                                                    int32_t scanCode, int32_t usageCode);
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 82f9276..bca9d91 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -35,10 +35,8 @@
 MultiTouchInputMapper::~MultiTouchInputMapper() {}
 
 std::list<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) {
-    // TODO(b/291626046): Sync the MT state with the kernel using EVIOCGMTSLOTS.
-    mMultiTouchMotionAccumulator.reset(getDeviceContext());
     mPointerIdBits.clear();
-
+    mMultiTouchMotionAccumulator.reset(mDeviceContext);
     return TouchInputMapper::reset(when);
 }
 
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 07ae5b1..0ddbc06 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -125,7 +125,7 @@
     if (scrolled) {
         int32_t metaState = getContext()->getGlobalMetaState();
         // This is not a pointer, so it's not associated with a display.
-        int32_t displayId = ADISPLAY_ID_NONE;
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
 
         if (mOrientation == ui::ROTATION_180) {
             scroll = -scroll;
diff --git a/services/inputflinger/reader/mapper/SlopController.cpp b/services/inputflinger/reader/mapper/SlopController.cpp
index f79219f..9ec02a6 100644
--- a/services/inputflinger/reader/mapper/SlopController.cpp
+++ b/services/inputflinger/reader/mapper/SlopController.cpp
@@ -54,11 +54,13 @@
     mCumulativeValue += value;
 
     if (abs(mCumulativeValue) >= mSlopThreshold) {
+        ALOGD("SlopController: did not drop event with value .%3f", value);
         mHasSlopBeenMet = true;
         // Return the amount of value that exceeds the slop.
         return signOf(value) * (abs(mCumulativeValue) - mSlopThreshold);
     }
 
+    ALOGD("SlopController: dropping event with value .%3f", value);
     return 0;
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp
index c12e95d..d60dc55 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp
@@ -28,7 +28,7 @@
 
 [[nodiscard]] std::list<NotifyArgs> synthesizeButtonKey(
         InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime,
-        int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+        int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
         int32_t lastButtonState, int32_t currentButtonState, int32_t buttonState, int32_t keyCode) {
     std::list<NotifyArgs> out;
     if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) &&
@@ -88,7 +88,7 @@
 
 [[nodiscard]] std::list<NotifyArgs> synthesizeButtonKeys(
         InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime,
-        int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+        int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
         int32_t lastButtonState, int32_t currentButtonState) {
     std::list<NotifyArgs> out;
     out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId,
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index 3023e68..13d952b 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -36,7 +36,7 @@
 
 [[nodiscard]] std::list<NotifyArgs> synthesizeButtonKeys(
         InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime,
-        int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+        int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
         int32_t lastButtonState, int32_t currentButtonState);
 
 // For devices connected over Bluetooth, although they may produce events at a consistent rate,
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 3c26d1d..8b4b691 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -20,8 +20,24 @@
 
 #include "TouchInputMapper.h"
 
+#include <algorithm>
+#include <cinttypes>
+#include <cmath>
+#include <cstddef>
+#include <tuple>
+
+#include <math.h>
+
+#include <android-base/stringprintf.h>
+#include <android/input.h>
 #include <ftl/enum.h>
 #include <input/PrintTools.h>
+#include <input/PropertyMap.h>
+#include <input/VirtualKeyMap.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+#include <math/vec2.h>
+#include <ui/FloatRect.h>
 
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
@@ -147,20 +163,6 @@
     info.addMotionRange(mOrientedRanges.y);
     info.addMotionRange(mOrientedRanges.pressure);
 
-    if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) {
-        // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode.
-        //
-        // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative
-        // motion, i.e. the hardware dimensions, as the finger could move completely across the
-        // touchpad in one sample cycle.
-        const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
-        const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
-        info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz,
-                            x.resolution);
-        info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz,
-                            y.resolution);
-    }
-
     if (mOrientedRanges.size) {
         info.addMotionRange(*mOrientedRanges.size);
     }
@@ -334,7 +336,6 @@
         changes.any(InputReaderConfiguration::Change::DISPLAY_INFO |
                     InputReaderConfiguration::Change::POINTER_CAPTURE |
                     InputReaderConfiguration::Change::POINTER_GESTURE_ENABLEMENT |
-                    InputReaderConfiguration::Change::SHOW_TOUCHES |
                     InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE |
                     InputReaderConfiguration::Change::DEVICE_TYPE)) {
         // Configure device sources, display dimensions, orientation and
@@ -531,14 +532,20 @@
  * 4. Otherwise, use a non-display viewport.
  */
 std::optional<DisplayViewport> TouchInputMapper::findViewport() {
-    if (mParameters.hasAssociatedDisplay && mDeviceMode != DeviceMode::UNSCALED) {
+    if (mParameters.hasAssociatedDisplay) {
         if (getDeviceContext().getAssociatedViewport()) {
             return getDeviceContext().getAssociatedViewport();
         }
 
-        const std::optional<std::string> associatedDisplayUniqueId =
-                getDeviceContext().getAssociatedDisplayUniqueId();
-        if (associatedDisplayUniqueId) {
+        const std::optional<std::string> associatedDisplayUniqueIdByDescriptor =
+                getDeviceContext().getAssociatedDisplayUniqueIdByDescriptor();
+        if (associatedDisplayUniqueIdByDescriptor) {
+            return getDeviceContext().getAssociatedViewport();
+        }
+
+        const std::optional<std::string> associatedDisplayUniqueIdByPort =
+                getDeviceContext().getAssociatedDisplayUniqueIdByPort();
+        if (associatedDisplayUniqueIdByPort) {
             return getDeviceContext().getAssociatedViewport();
         }
 
@@ -548,8 +555,8 @@
             if (viewport) {
                 return viewport;
             } else {
-                ALOGW("Can't find designated display viewport with ID %" PRId32 " for pointers.",
-                      mConfig.defaultPointerDisplayId);
+                ALOGW("Can't find designated display viewport with ID %s for pointers.",
+                      mConfig.defaultPointerDisplayId.toString().c_str());
             }
         }
 
@@ -923,7 +930,7 @@
 
     // Determine device mode.
     if (mParameters.deviceType == Parameters::DeviceType::POINTER &&
-        mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.enable) {
+        mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.isEnable()) {
         mSource = AINPUT_SOURCE_MOUSE;
         mDeviceMode = DeviceMode::POINTER;
         if (hasStylus()) {
@@ -939,8 +946,10 @@
         mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
         mDeviceMode = DeviceMode::NAVIGATION;
     } else {
-        mSource = AINPUT_SOURCE_TOUCHPAD;
-        mDeviceMode = DeviceMode::UNSCALED;
+        ALOGW("Touch device '%s' has invalid parameters or configuration.  The device will be "
+              "inoperable.",
+              getDeviceName().c_str());
+        mDeviceMode = DeviceMode::DISABLED;
     }
 
     const std::optional<DisplayViewport> newViewportOpt = findViewport();
@@ -1032,37 +1041,12 @@
         mOrientedRanges.clear();
     }
 
-    // Create and preserve the pointer controller in the following cases:
-    const bool isPointerControllerNeeded =
-            // - when the device is in pointer mode, to show the mouse cursor;
-            (mDeviceMode == DeviceMode::POINTER) ||
-            // - when pointer capture is enabled, to preserve the mouse cursor position;
-            (mParameters.deviceType == Parameters::DeviceType::POINTER &&
-             mConfig.pointerCaptureRequest.enable) ||
-            // - when we should be showing touches;
-            (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
-            // - when we should be showing a pointer icon for direct styluses.
-            (mDeviceMode == DeviceMode::DIRECT && mConfig.stylusPointerIconEnabled && hasStylus());
-    if (isPointerControllerNeeded) {
-        if (mPointerController == nullptr) {
-            mPointerController = getContext()->getPointerController(getDeviceId());
-        }
-        if (mConfig.pointerCaptureRequest.enable) {
-            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-        }
-    } else {
-        if (mPointerController != nullptr && mDeviceMode == DeviceMode::DIRECT &&
-            !mConfig.showTouches) {
-            mPointerController->clearSpots();
-        }
-        mPointerController.reset();
-    }
-
     if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) {
-        ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %d, mode %d, "
-              "display id %d",
+        ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %s, mode %s, "
+              "display id %s",
               getDeviceId(), getDeviceName().c_str(), toString(mDisplayBounds).c_str(),
-              mInputDeviceOrientation, mDeviceMode, mViewport.displayId);
+              ftl::enum_string(mInputDeviceOrientation).c_str(),
+              ftl::enum_string(mDeviceMode).c_str(), mViewport.displayId.toString().c_str());
 
         configureVirtualKeys();
 
@@ -1113,7 +1097,8 @@
     dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str());
     dump += StringPrintf(INDENT3 "PhysicalFrameInRotatedDisplay: %s\n",
                          toString(mPhysicalFrameInRotatedDisplay).c_str());
-    dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation);
+    dump += StringPrintf(INDENT3 "InputDeviceOrientation: %s\n",
+                         ftl::enum_string(mInputDeviceOrientation).c_str());
 }
 
 void TouchInputMapper::configureVirtualKeys() {
@@ -1388,7 +1373,6 @@
 
 std::list<NotifyArgs> TouchInputMapper::reset(nsecs_t when) {
     std::list<NotifyArgs> out = cancelTouch(when, when);
-    updateTouchSpots();
 
     mCursorButtonAccumulator.reset(getDeviceContext());
     mCursorScrollAccumulator.reset(getDeviceContext());
@@ -1415,11 +1399,6 @@
     mPointerSimple.reset();
     resetExternalStylus();
 
-    if (mPointerController != nullptr) {
-        mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-        mPointerController->clearSpots();
-    }
-
     return out += InputMapper::reset(when);
 }
 
@@ -1574,11 +1553,6 @@
     uint32_t policyFlags = 0;
     bool buttonsPressed = mCurrentRawState.buttonState & ~mLastRawState.buttonState;
     if (initialDown || buttonsPressed) {
-        // If this is a touch screen, hide the pointer on an initial down.
-        if (mDeviceMode == DeviceMode::DIRECT) {
-            getContext()->fadePointer();
-        }
-
         if (mParameters.wake) {
             policyFlags |= POLICY_FLAG_WAKE;
         }
@@ -1646,7 +1620,6 @@
         out += dispatchPointerUsage(when, readTime, policyFlags, pointerUsage);
     } else {
         if (!mCurrentMotionAborted) {
-            updateTouchSpots();
             out += dispatchButtonRelease(when, readTime, policyFlags);
             out += dispatchHoverExit(when, readTime, policyFlags);
             out += dispatchTouches(when, readTime, policyFlags);
@@ -1678,28 +1651,6 @@
     return out;
 }
 
-void TouchInputMapper::updateTouchSpots() {
-    if (!mConfig.showTouches || mPointerController == nullptr) {
-        return;
-    }
-
-    // Update touch spots when this is a touchscreen even when it's not enabled so that we can
-    // clear touch spots.
-    if (mDeviceMode != DeviceMode::DIRECT &&
-        (mDeviceMode != DeviceMode::DISABLED || !isTouchScreen())) {
-        return;
-    }
-
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT);
-    mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-
-    mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(),
-                                 mCurrentCookedState.cookedPointerData.idToIndex.cbegin(),
-                                 mCurrentCookedState.cookedPointerData.touchingIdBits |
-                                         mCurrentCookedState.cookedPointerData.hoveringIdBits,
-                                 mViewport.displayId);
-}
-
 bool TouchInputMapper::isTouchScreen() {
     return mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN &&
             mParameters.hasAssociatedDisplay;
@@ -1884,8 +1835,7 @@
     }
 
     if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() &&
-        mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() &&
-        mDeviceMode != DeviceMode::UNSCALED) {
+        mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
         // We have hovering pointers, and there are no touching pointers.
         bool hoveringPointersInFrame = false;
         auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits;
@@ -1912,7 +1862,7 @@
         // Skip checking whether the pointer is inside the physical frame if the device is in
         // unscaled or pointer mode.
         if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
-            mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) {
+            mDeviceMode != DeviceMode::POINTER) {
             // If exactly one pointer went down, check for virtual key hit.
             // Otherwise, we will drop the entire stroke.
             if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
@@ -2549,54 +2499,6 @@
         cancelPreviousGesture = false;
     }
 
-    // Update the pointer presentation and spots.
-    if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH) {
-        mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-        if (finishPreviousGesture || cancelPreviousGesture) {
-            mPointerController->clearSpots();
-        }
-
-        if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
-            mPointerController->setSpots(mPointerGesture.currentGestureCoords.cbegin(),
-                                         mPointerGesture.currentGestureIdToIndex.cbegin(),
-                                         mPointerGesture.currentGestureIdBits,
-                                         mPointerController->getDisplayId());
-        }
-    } else {
-        mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    }
-
-    // Show or hide the pointer if needed.
-    switch (mPointerGesture.currentGestureMode) {
-        case PointerGesture::Mode::NEUTRAL:
-        case PointerGesture::Mode::QUIET:
-            if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH &&
-                mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) {
-                // Remind the user of where the pointer is after finishing a gesture with spots.
-                mPointerController->unfade(PointerControllerInterface::Transition::GRADUAL);
-            }
-            break;
-        case PointerGesture::Mode::TAP:
-        case PointerGesture::Mode::TAP_DRAG:
-        case PointerGesture::Mode::BUTTON_CLICK_OR_DRAG:
-        case PointerGesture::Mode::HOVER:
-        case PointerGesture::Mode::PRESS:
-        case PointerGesture::Mode::SWIPE:
-            // Unfade the pointer when the current gesture manipulates the
-            // area directly under the pointer.
-            mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-            break;
-        case PointerGesture::Mode::FREEFORM:
-            // Fade the pointer when the current gesture manipulates a different
-            // area and there are spots to guide the user experience.
-            if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH) {
-                mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-            } else {
-                mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-            }
-            break;
-    }
-
     // Send events!
     int32_t metaState = getContext()->getGlobalMetaState();
     int32_t buttonState = mCurrentCookedState.buttonState;
@@ -2735,7 +2637,6 @@
         // the pointer is hovering again even if the user is not currently touching
         // the touch pad.  This ensures that a view will receive a fresh hover enter
         // event after a tap.
-        const auto [x, y] = mPointerController->getPosition();
 
         PointerProperties pointerProperties;
         pointerProperties.clear();
@@ -2744,16 +2645,12 @@
 
         PointerCoords pointerCoords;
         pointerCoords.clear();
-        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-
-        const int32_t displayId = mPointerController->getDisplayId();
         out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
-                                       mSource, displayId, policyFlags,
+                                       mSource, ui::LogicalDisplayId::INVALID, policyFlags,
                                        AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState,
                                        buttonState, MotionClassification::NONE,
                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
-                                       &pointerCoords, 0, 0, x, y, mPointerGesture.downTime,
+                                       &pointerCoords, 0, 0, 0.f, 0.f, mPointerGesture.downTime,
                                        /*videoFrames=*/{}));
     }
 
@@ -2799,12 +2696,6 @@
     // Reset the current pointer gesture.
     mPointerGesture.reset();
     mPointerVelocityControl.reset();
-
-    // Remove any current spots.
-    if (mPointerController != nullptr) {
-        mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-        mPointerController->clearSpots();
-    }
     return out;
 }
 
@@ -2940,8 +2831,6 @@
             mPointerVelocityControl.reset();
         }
 
-        const auto [x, y] = mPointerController->getPosition();
-
         mPointerGesture.currentGestureMode = PointerGesture::Mode::BUTTON_CLICK_OR_DRAG;
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
@@ -2950,8 +2839,6 @@
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
         mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER;
         mPointerGesture.currentGestureCoords[0].clear();
-        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
     } else if (currentFingerCount == 0) {
         // Case 3. No fingers down and button is not pressed. (NEUTRAL)
@@ -2966,9 +2853,8 @@
              mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) &&
             lastFingerCount == 1) {
             if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) {
-                const auto [x, y] = mPointerController->getPosition();
-                if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
-                    fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+                if (fabs(0.f - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
+                    fabs(0.f - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
                     ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP");
 
                     mPointerGesture.tapUpTime = when;
@@ -2995,7 +2881,7 @@
                     tapped = true;
                 } else {
                     ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP, deltaX=%f, deltaY=%f",
-                             x - mPointerGesture.tapX, y - mPointerGesture.tapY);
+                             0.f - mPointerGesture.tapX, 0.f - mPointerGesture.tapY);
                 }
             } else {
                 if (DEBUG_GESTURES) {
@@ -3027,13 +2913,12 @@
         mPointerGesture.currentGestureMode = PointerGesture::Mode::HOVER;
         if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) {
             if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
-                const auto [x, y] = mPointerController->getPosition();
-                if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
-                    fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+                if (fabs(0.f - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
+                    fabs(0.f - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
                     mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG;
                 } else {
                     ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
-                             x - mPointerGesture.tapX, y - mPointerGesture.tapY);
+                             0.f - mPointerGesture.tapX, 0.f - mPointerGesture.tapY);
                 }
             } else {
                 ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, %0.3fms time since up",
@@ -3063,8 +2948,6 @@
             down = false;
         }
 
-        const auto [x, y] = mPointerController->getPosition();
-
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@@ -3072,16 +2955,14 @@
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
         mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER;
         mPointerGesture.currentGestureCoords[0].clear();
-        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
                                                              down ? 1.0f : 0.0f);
 
         if (lastFingerCount == 0 && currentFingerCount != 0) {
             mPointerGesture.resetTap();
             mPointerGesture.tapDownTime = when;
-            mPointerGesture.tapX = x;
-            mPointerGesture.tapY = y;
+            mPointerGesture.tapX = 0.f;
+            mPointerGesture.tapY = 0.f;
         }
     } else {
         // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
@@ -3090,11 +2971,13 @@
 
     if (DEBUG_GESTURES) {
         ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
-              "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
-              "lastGestureMode=%d, lastGestureIdBits=0x%08x",
+              "currentGestureMode=%s, currentGestureIdBits=0x%08x, "
+              "lastGestureMode=%s, lastGestureIdBits=0x%08x",
               toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
-              mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
-              mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
+              ftl::enum_string(mPointerGesture.currentGestureMode).c_str(),
+              mPointerGesture.currentGestureIdBits.value,
+              ftl::enum_string(mPointerGesture.lastGestureMode).c_str(),
+              mPointerGesture.lastGestureIdBits.value);
         for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) {
             uint32_t id = idBits.clearFirstMarkedBit();
             uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
@@ -3230,8 +3113,8 @@
         mCurrentRawState.rawPointerData
                 .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
                                                &mPointerGesture.referenceTouchY);
-        std::tie(mPointerGesture.referenceGestureX, mPointerGesture.referenceGestureY) =
-                mPointerController->getPosition();
+        mPointerGesture.referenceGestureX = 0.f;
+        mPointerGesture.referenceGestureY = 0.f;
     }
 
     // Clear the reference deltas for fingers not yet included in the reference calculation.
@@ -3526,8 +3409,6 @@
 
     rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
     mPointerVelocityControl.move(when, &deltaX, &deltaY);
-
-    mPointerController->move(deltaX, deltaY);
 }
 
 std::list<NotifyArgs> TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime,
@@ -3544,13 +3425,6 @@
 
         float x = mCurrentCookedState.cookedPointerData.pointerCoords[index].getX();
         float y = mCurrentCookedState.cookedPointerData.pointerCoords[index].getY();
-        // Styluses are configured specifically for one display. We only update the
-        // PointerController for this stylus if the PointerController is configured for
-        // the same display as this stylus,
-        if (getAssociatedDisplayId() == mViewport.displayId) {
-            mPointerController->setPosition(x, y);
-            std::tie(x, y) = mPointerController->getPosition();
-        }
 
         mPointerSimple.currentCoords = mCurrentCookedState.cookedPointerData.pointerCoords[index];
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3588,12 +3462,9 @@
         down = isPointerDown(mCurrentRawState.buttonState);
         hovering = !down;
 
-        const auto [x, y] = mPointerController->getPosition();
         const uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id];
         mPointerSimple.currentCoords =
                 mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex];
-        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
                                                   hovering ? 0.0f : 1.0f);
         mPointerSimple.currentProperties.id = 0;
@@ -3606,8 +3477,8 @@
         hovering = false;
     }
 
-    const int32_t displayId = mPointerController->getDisplayId();
-    return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, displayId);
+    return dispatchPointerSimple(when, readTime, policyFlags, down, hovering,
+                                 ui::LogicalDisplayId::INVALID);
 }
 
 std::list<NotifyArgs> TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime,
@@ -3621,24 +3492,14 @@
 
 std::list<NotifyArgs> TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
                                                               uint32_t policyFlags, bool down,
-                                                              bool hovering, int32_t displayId) {
+                                                              bool hovering,
+                                                              ui::LogicalDisplayId displayId) {
     LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER,
                         "%s cannot be used when the device is not in POINTER mode.", __func__);
     std::list<NotifyArgs> out;
     int32_t metaState = getContext()->getGlobalMetaState();
     auto cursorPosition = mPointerSimple.currentCoords.getXYValue();
 
-    if (displayId == mPointerController->getDisplayId()) {
-        std::tie(cursorPosition.x, cursorPosition.y) = mPointerController->getPosition();
-        if (down || hovering) {
-            mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-            mPointerController->clearSpots();
-            mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-        } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) {
-            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-        }
-    }
-
     if (mPointerSimple.down && !down) {
         mPointerSimple.down = false;
 
@@ -3774,9 +3635,6 @@
                                        mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
                                        mPointerSimple.downTime,
                                        /*videoFrames=*/{}));
-        if (mPointerController != nullptr) {
-            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-        }
     }
     mPointerSimple.reset();
     return out;
@@ -3827,35 +3685,15 @@
         source |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
     }
 
-    const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
-    const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
-            mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, pointerProperties) &&
-            mPointerController && displayId != ADISPLAY_ID_NONE &&
-            displayId == mPointerController->getDisplayId();
-    if (showDirectStylusPointer) {
-        switch (action & AMOTION_EVENT_ACTION_MASK) {
-            case AMOTION_EVENT_ACTION_HOVER_ENTER:
-            case AMOTION_EVENT_ACTION_HOVER_MOVE:
-                mPointerController->setPresentation(
-                        PointerControllerInterface::Presentation::STYLUS_HOVER);
-                mPointerController
-                        ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[0].getX(),
-                                      mCurrentCookedState.cookedPointerData.pointerCoords[0]
-                                              .getY());
-                mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-                break;
-            case AMOTION_EVENT_ACTION_HOVER_EXIT:
-                mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-                break;
-        }
-    }
+    const ui::LogicalDisplayId displayId =
+            getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID);
 
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mDeviceMode == DeviceMode::POINTER) {
-        std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
+        xCursorPosition = yCursorPosition = 0.f;
     }
-    const int32_t deviceId = getDeviceId();
+    const DeviceId deviceId = getDeviceId();
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
     std::for_each(frames.begin(), frames.end(),
                   [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); });
@@ -4122,10 +3960,10 @@
     return true;
 }
 
-std::optional<int32_t> TouchInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> TouchInputMapper::getAssociatedDisplayId() {
     if (mParameters.hasAssociatedDisplay) {
         if (mDeviceMode == DeviceMode::POINTER) {
-            return std::make_optional(mPointerController->getDisplayId());
+            return ui::LogicalDisplayId::INVALID;
         } else {
             return std::make_optional(mViewport.displayId);
         }
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index bd9371d..b24f2ff 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -16,17 +16,36 @@
 
 #pragma once
 
+#include <array>
+#include <climits>
+#include <limits>
+#include <list>
+#include <memory>
 #include <optional>
 #include <string>
+#include <utility>
+#include <vector>
 
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <input/VelocityControl.h>
+#include <input/VelocityTracker.h>
 #include <stdint.h>
+#include <ui/Rect.h>
 #include <ui/Rotation.h>
+#include <ui/Size.h>
+#include <ui/Transform.h>
+#include <utils/BitSet.h>
+#include <utils/Timers.h>
 
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
 #include "EventHub.h"
 #include "InputMapper.h"
 #include "InputReaderBase.h"
+#include "NotifyArgs.h"
+#include "StylusState.h"
 #include "TouchButtonAccumulator.h"
 
 namespace android {
@@ -166,7 +185,7 @@
     [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> updateExternalStylusState(
             const StylusState& state) override;
-    std::optional<int32_t> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
 
 protected:
     CursorButtonAccumulator mCursorButtonAccumulator;
@@ -195,7 +214,6 @@
     enum class DeviceMode {
         DISABLED,   // input is disabled
         DIRECT,     // direct mapping (touchscreen)
-        UNSCALED,   // unscaled mapping (e.g. captured touchpad)
         NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
         POINTER,    // pointer mapping (e.g. uncaptured touchpad, drawing tablet)
 
@@ -372,9 +390,6 @@
     // The time the primary pointer last went down.
     nsecs_t mDownTime{0};
 
-    // The pointer controller, or null if the device is not a pointer.
-    std::shared_ptr<PointerControllerInterface> mPointerController;
-
     std::vector<VirtualKey> mVirtualKeys;
 
     explicit TouchInputMapper(InputDeviceContext& deviceContext,
@@ -569,6 +584,8 @@
 
             // Waiting for quiet time to end before starting the next gesture.
             QUIET,
+
+            ftl_last = QUIET,
         };
 
         // When a gesture is sent to an unfocused window, return true if it can bring that window
@@ -688,7 +705,7 @@
 
         // Values reported for the last pointer event.
         uint32_t source;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
         float lastCursorX;
         float lastCursorY;
 
@@ -701,16 +718,16 @@
             hovering = false;
             downTime = 0;
             source = 0;
-            displayId = ADISPLAY_ID_NONE;
+            displayId = ui::LogicalDisplayId::INVALID;
             lastCursorX = 0.f;
             lastCursorY = 0.f;
         }
     } mPointerSimple;
 
     // The pointer and scroll velocity controls.
-    VelocityControl mPointerVelocityControl;
-    VelocityControl mWheelXVelocityControl;
-    VelocityControl mWheelYVelocityControl;
+    SimpleVelocityControl mPointerVelocityControl;
+    SimpleVelocityControl mWheelXVelocityControl;
+    SimpleVelocityControl mWheelYVelocityControl;
 
     std::optional<DisplayViewport> findViewport();
 
@@ -792,7 +809,8 @@
 
     [[nodiscard]] std::list<NotifyArgs> dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
                                                               uint32_t policyFlags, bool down,
-                                                              bool hovering, int32_t displayId);
+                                                              bool hovering,
+                                                              ui::LogicalDisplayId displayId);
     [[nodiscard]] std::list<NotifyArgs> abortPointerSimple(nsecs_t when, nsecs_t readTime,
                                                            uint32_t policyFlags);
 
@@ -815,9 +833,6 @@
 
     // Returns if this touch device is a touch screen with an associated display.
     bool isTouchScreen();
-    // Updates touch spots if they are enabled. Should only be used when this device is a
-    // touchscreen.
-    void updateTouchSpots();
 
     bool isPointInsidePhysicalFrame(int32_t x, int32_t y) const;
     const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y);
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 34ca0b3..b8911db 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -21,12 +21,15 @@
 #include <iterator>
 #include <limits>
 #include <map>
+#include <mutex>
 #include <optional>
 
 #include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
 #include <android/input.h>
 #include <com_android_input_flags.h>
 #include <ftl/enum.h>
+#include <input/AccelerationCurve.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
@@ -53,27 +56,10 @@
         __android_log_is_loggable(ANDROID_LOG_DEBUG, "TouchpadInputMapperGestures",
                                   ANDROID_LOG_INFO);
 
-// Describes a segment of the acceleration curve.
-struct CurveSegment {
-    // The maximum pointer speed which this segment should apply. The last segment in a curve should
-    // always set this to infinity.
-    double maxPointerSpeedMmPerS;
-    double slope;
-    double intercept;
-};
-
-const std::vector<CurveSegment> segments = {
-        {32.002, 3.19, 0},
-        {52.83, 4.79, -51.254},
-        {119.124, 7.28, -182.737},
-        {std::numeric_limits<double>::infinity(), 15.04, -1107.556},
-};
-
-const std::vector<double> sensitivityFactors = {1,  2,  4,  6,  7,  8,  9, 10,
-                                                11, 12, 13, 14, 16, 18, 20};
-
 std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity,
                                                           size_t propertySize) {
+    std::vector<AccelerationCurveSegment> segments =
+            createAccelerationCurveForPointerSensitivity(sensitivity);
     LOG_ALWAYS_FATAL_IF(propertySize < 4 * segments.size());
     std::vector<double> output(propertySize, 0);
 
@@ -83,31 +69,23 @@
     //
     // (a, b, and c are also called sqr_, mul_, and int_ in the Gestures library code.)
     //
-    // We are trying to implement the following function, where slope and intercept are the
-    // parameters specified in the `segments` array above:
-    //     gain(input_speed_mm) =
-    //             0.64 * (sensitivityFactor / 10) * (slope + intercept / input_speed_mm)
+    // createAccelerationCurveForPointerSensitivity gives us parameters for a function of the form:
+    //     gain(input_speed_mm) = baseGain + reciprocal / input_speed_mm
     // Where "gain" is a multiplier applied to the input speed to produce the output speed:
     //     output_speed(input_speed_mm) = input_speed_mm * gain(input_speed_mm)
     //
     // To put our function in the library's form, we substitute it into the function above:
-    //     output_speed(input_speed_mm) =
-    //             input_speed_mm * (0.64 * (sensitivityFactor / 10) *
-    //             (slope + 25.4 * intercept / input_speed_mm))
-    // then expand the brackets so that input_speed_mm cancels out for the intercept term:
-    //     gain(input_speed_mm) =
-    //             0.64 * (sensitivityFactor / 10) * slope * input_speed_mm +
-    //             0.64 * (sensitivityFactor / 10) * intercept
+    //     output_speed(input_speed_mm) = input_speed_mm * (baseGain + reciprocal / input_speed_mm)
+    // then expand the brackets so that input_speed_mm cancels out for the reciprocal term:
+    //     gain(input_speed_mm) = baseGain * input_speed_mm + reciprocal
     //
     // This gives us the following parameters for the Gestures library function form:
     //     a = 0
-    //     b = 0.64 * (sensitivityFactor / 10) * slope
-    //     c = 0.64 * (sensitivityFactor / 10) * intercept
-
-    double commonFactor = 0.64 * sensitivityFactors[sensitivity + 7] / 10;
+    //     b = baseGain
+    //     c = reciprocal
 
     size_t i = 0;
-    for (CurveSegment seg : segments) {
+    for (AccelerationCurveSegment seg : segments) {
         // The library's curve format consists of four doubles per segment:
         // * maximum pointer speed for the segment (mm/s)
         // * multiplier for the x² term (a.k.a. "a" or "sqr")
@@ -116,8 +94,8 @@
         // (see struct CurveSegment in the library's AccelFilterInterpreter)
         output[i + 0] = seg.maxPointerSpeedMmPerS;
         output[i + 1] = 0;
-        output[i + 2] = commonFactor * seg.slope;
-        output[i + 3] = commonFactor * seg.intercept;
+        output[i + 2] = seg.baseGain;
+        output[i + 3] = seg.reciprocal;
         i += 4;
     }
 
@@ -156,13 +134,20 @@
         return sAccumulator;
     }
 
-    void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].fingers++; }
+    void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) {
+        std::scoped_lock lock(mLock);
+        mCounters[id].fingers++;
+    }
 
-    void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].palms++; }
+    void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) {
+        std::scoped_lock lock(mLock);
+        mCounters[id].palms++;
+    }
 
     // Checks whether a Gesture struct is for the end of a gesture that we log metrics for, and
     // records it if so.
     void processGesture(const TouchpadInputMapper::MetricsIdentifier& id, const Gesture& gesture) {
+        std::scoped_lock lock(mLock);
         switch (gesture.type) {
             case kGestureTypeFling:
                 if (gesture.details.fling.fling_state == GESTURES_FLING_START) {
@@ -200,15 +185,20 @@
                                                                  void* cookie) {
         LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE);
         MetricsAccumulator& accumulator = MetricsAccumulator::getInstance();
-        accumulator.produceAtoms(outEventList);
-        accumulator.resetCounters();
+        accumulator.produceAtomsAndReset(*outEventList);
         return AStatsManager_PULL_SUCCESS;
     }
 
-    void produceAtoms(AStatsEventList* outEventList) const {
+    void produceAtomsAndReset(AStatsEventList& outEventList) {
+        std::scoped_lock lock(mLock);
+        produceAtomsLocked(outEventList);
+        resetCountersLocked();
+    }
+
+    void produceAtomsLocked(AStatsEventList& outEventList) const REQUIRES(mLock) {
         for (auto& [id, counters] : mCounters) {
             auto [busId, vendorId, productId, versionId] = id;
-            addAStatsEvent(outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
+            addAStatsEvent(&outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
                            versionId, linuxBusToInputDeviceBusEnum(busId, /*isUsi=*/false),
                            counters.fingers, counters.palms, counters.twoFingerSwipeGestures,
                            counters.threeFingerSwipeGestures, counters.fourFingerSwipeGestures,
@@ -216,7 +206,7 @@
         }
     }
 
-    void resetCounters() { mCounters.clear(); }
+    void resetCountersLocked() REQUIRES(mLock) { mCounters.clear(); }
 
     // Stores the counters for a specific touchpad model. Fields have the same meanings as those of
     // the TouchpadUsage atom; see that definition for detailed documentation.
@@ -232,7 +222,10 @@
 
     // Metrics are aggregated by device model and version, so if two devices of the same model and
     // version are connected at once, they will have the same counters.
-    std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters;
+    std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters GUARDED_BY(mLock);
+
+    // Metrics are pulled by a binder thread, so we need to guard them with a mutex.
+    mutable std::mutex mLock;
 };
 
 } // namespace
@@ -241,13 +234,11 @@
                                          const InputReaderConfiguration& readerConfig)
       : InputMapper(deviceContext, readerConfig),
         mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
-        mPointerController(getContext()->getPointerController(getDeviceId())),
         mTimerProvider(*getContext()),
         mStateConverter(deviceContext, mMotionAccumulator),
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
         mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
-        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())),
-        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {
+        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) {
     RawAbsoluteAxisInfo slotAxisInfo;
     deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
     if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
@@ -274,10 +265,6 @@
 }
 
 TouchpadInputMapper::~TouchpadInputMapper() {
-    if (mPointerController != nullptr) {
-        mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-    }
-
     // The gesture interpreter's destructor will try to free its property and timer providers,
     // calling PropertyProvider::freeProperty and TimerProvider::freeTimer using a raw pointers.
     // Depending on the declaration order in TouchpadInputMapper.h, those providers may have already
@@ -319,7 +306,8 @@
     }
     dump += INDENT3 "Captured event converter:\n";
     dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4);
-    dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
+    dump += StringPrintf(INDENT3 "DisplayId: %s\n",
+                         toString(mDisplayId, streamableToString).c_str());
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::reconfigure(nsecs_t when,
@@ -331,7 +319,7 @@
     }
 
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
-        mDisplayId = ADISPLAY_ID_NONE;
+        mDisplayId = ui::LogicalDisplayId::INVALID;
         std::optional<DisplayViewport> resolvedViewport;
         std::optional<FloatRect> boundsInLogicalDisplay;
         if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
@@ -339,33 +327,13 @@
             // Only generate events for the associated display.
             mDisplayId = assocViewport->displayId;
             resolvedViewport = *assocViewport;
-            if (!mEnablePointerChoreographer) {
-                const bool mismatchedPointerDisplay =
-                        (assocViewport->displayId != mPointerController->getDisplayId());
-                if (mismatchedPointerDisplay) {
-                    ALOGW("Touchpad \"%s\" associated viewport display does not match pointer "
-                          "controller",
-                          mDeviceContext.getName().c_str());
-                    mDisplayId.reset();
-                }
-            }
         } else {
             // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
-            if (mEnablePointerChoreographer) {
-                // Always use DISPLAY_ID_NONE for touchpad events.
-                // PointerChoreographer will make it target the correct the displayId later.
-                resolvedViewport =
-                        getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
-                mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
-            } else {
-                mDisplayId = mPointerController->getDisplayId();
-                if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
-                    resolvedViewport = *v;
-                }
-                if (auto bounds = mPointerController->getBounds(); bounds) {
-                    boundsInLogicalDisplay = *bounds;
-                }
-            }
+            // Always use DISPLAY_ID_NONE for touchpad events.
+            // PointerChoreographer will make it target the correct the displayId later.
+            resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+            mDisplayId = resolvedViewport ? std::make_optional(ui::LogicalDisplayId::INVALID)
+                                          : std::nullopt;
         }
 
         mGestureConverter.setDisplayId(mDisplayId);
@@ -382,6 +350,8 @@
                     : FloatRect{0, 0, 0, 0};
         }
         mGestureConverter.setBoundsInLogicalDisplay(*boundsInLogicalDisplay);
+
+        bumpGeneration();
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) {
         mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve")
@@ -402,20 +372,21 @@
                 .setBoolValues({config.touchpadNaturalScrollingEnabled});
         mPropertyProvider.getProperty("Tap Enable")
                 .setBoolValues({config.touchpadTapToClickEnabled});
+        mPropertyProvider.getProperty("Tap Drag Enable")
+                .setBoolValues({config.touchpadTapDraggingEnabled});
         mPropertyProvider.getProperty("Button Right Click Zone Enable")
                 .setBoolValues({config.touchpadRightClickZoneEnabled});
     }
     std::list<NotifyArgs> out;
-    if ((!changes.any() && config.pointerCaptureRequest.enable) ||
+    if ((!changes.any() && config.pointerCaptureRequest.isEnable()) ||
         changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) {
-        mPointerCaptured = config.pointerCaptureRequest.enable;
+        mPointerCaptured = config.pointerCaptureRequest.isEnable();
         // The motion ranges are going to change, so bump the generation to clear the cached ones.
         bumpGeneration();
         if (mPointerCaptured) {
             // The touchpad is being captured, so we need to tidy up any fake fingers etc. that are
             // still being reported for a gesture in progress.
             out += reset(when);
-            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
         } else {
             // We're transitioning from captured to uncaptured.
             mCapturedEventConverter.reset();
@@ -449,6 +420,9 @@
     if (mPointerCaptured) {
         return mCapturedEventConverter.process(*rawEvent);
     }
+    if (mMotionAccumulator.getActiveSlotsCount() == 0) {
+        mGestureStartTime = rawEvent->when;
+    }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
         updatePalmDetectionMetrics();
@@ -514,7 +488,7 @@
     if (mDisplayId) {
         MetricsAccumulator& metricsAccumulator = MetricsAccumulator::getInstance();
         for (Gesture& gesture : mGesturesToProcess) {
-            out += mGestureConverter.handleGesture(when, readTime, gesture);
+            out += mGestureConverter.handleGesture(when, readTime, mGestureStartTime, gesture);
             metricsAccumulator.processGesture(mMetricsId, gesture);
         }
     }
@@ -522,7 +496,7 @@
     return out;
 }
 
-std::optional<int32_t> TouchpadInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> TouchpadInputMapper::getAssociatedDisplayId() {
     return mDisplayId;
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index ece0eca..546fa5b 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -66,7 +66,7 @@
     using MetricsIdentifier = std::tuple<uint16_t /*busId*/, uint16_t /*vendorId*/,
                                          uint16_t /*productId*/, uint16_t /*version*/>;
 
-    std::optional<int32_t> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
 
 private:
     void resetGestureInterpreter(nsecs_t when);
@@ -79,7 +79,6 @@
 
     std::unique_ptr<gestures::GestureInterpreter, void (*)(gestures::GestureInterpreter*)>
             mGestureInterpreter;
-    std::shared_ptr<PointerControllerInterface> mPointerController;
 
     PropertyProvider mPropertyProvider;
     TimerProvider mTimerProvider;
@@ -107,12 +106,12 @@
     // Tracking IDs for touches that have at some point been reported as palms by the touchpad.
     std::set<int32_t> mPalmTrackingIds;
 
-    const bool mEnablePointerChoreographer;
-
     // The display that events generated by this mapper should target. This can be set to
-    // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
+    // LogicalDisplayId::INVALID to target the focused display. If there is no display target (i.e.
     // std::nullopt), all events will be ignored.
-    std::optional<int32_t> mDisplayId;
+    std::optional<ui::LogicalDisplayId> mDisplayId;
+
+    nsecs_t mGestureStartTime{0};
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index d06514a..b3f1700 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -30,30 +30,12 @@
                                             size_t slotCount, bool usingSlotsProtocol) {
     mUsingSlotsProtocol = usingSlotsProtocol;
     mSlots = std::vector<Slot>(slotCount);
-    reset(deviceContext);
+    populateCurrentSlot(deviceContext);
 }
 
 void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) {
     resetSlots();
-
-    if (!mUsingSlotsProtocol) {
-        return;
-    }
-
-    // Query the driver for the current slot index and use it as the initial slot before we
-    // start reading events from the device.  It is possible that the current slot index will
-    // not be the same as it was when the first event was written into the evdev buffer, which
-    // means the input mapper could start out of sync with the initial state of the events in
-    // the evdev buffer. In the extremely unlikely case that this happens, the data from two
-    // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the
-    // touch point to "jump", but at least there will be no stuck touches.
-    int32_t initialSlot;
-    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-        status == OK) {
-        mCurrentSlot = initialSlot;
-    } else {
-        ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
-    }
+    syncSlots(deviceContext);
 }
 
 void MultiTouchMotionAccumulator::resetSlots() {
@@ -90,54 +72,10 @@
             if (!mUsingSlotsProtocol) {
                 slot.mInUse = true;
             }
-
-            switch (rawEvent->code) {
-                case ABS_MT_POSITION_X:
-                    slot.mAbsMtPositionX = rawEvent->value;
-                    warnIfNotInUse(*rawEvent, slot);
-                    break;
-                case ABS_MT_POSITION_Y:
-                    slot.mAbsMtPositionY = rawEvent->value;
-                    warnIfNotInUse(*rawEvent, slot);
-                    break;
-                case ABS_MT_TOUCH_MAJOR:
-                    slot.mAbsMtTouchMajor = rawEvent->value;
-                    break;
-                case ABS_MT_TOUCH_MINOR:
-                    slot.mAbsMtTouchMinor = rawEvent->value;
-                    slot.mHaveAbsMtTouchMinor = true;
-                    break;
-                case ABS_MT_WIDTH_MAJOR:
-                    slot.mAbsMtWidthMajor = rawEvent->value;
-                    break;
-                case ABS_MT_WIDTH_MINOR:
-                    slot.mAbsMtWidthMinor = rawEvent->value;
-                    slot.mHaveAbsMtWidthMinor = true;
-                    break;
-                case ABS_MT_ORIENTATION:
-                    slot.mAbsMtOrientation = rawEvent->value;
-                    break;
-                case ABS_MT_TRACKING_ID:
-                    if (mUsingSlotsProtocol && rawEvent->value < 0) {
-                        // The slot is no longer in use but it retains its previous contents,
-                        // which may be reused for subsequent touches.
-                        slot.mInUse = false;
-                    } else {
-                        slot.mInUse = true;
-                        slot.mAbsMtTrackingId = rawEvent->value;
-                    }
-                    break;
-                case ABS_MT_PRESSURE:
-                    slot.mAbsMtPressure = rawEvent->value;
-                    break;
-                case ABS_MT_DISTANCE:
-                    slot.mAbsMtDistance = rawEvent->value;
-                    break;
-                case ABS_MT_TOOL_TYPE:
-                    slot.mAbsMtToolType = rawEvent->value;
-                    slot.mHaveAbsMtToolType = true;
-                    break;
+            if (rawEvent->code == ABS_MT_POSITION_X || rawEvent->code == ABS_MT_POSITION_Y) {
+                warnIfNotInUse(*rawEvent, slot);
             }
+            slot.populateAxisValue(rawEvent->code, rawEvent->value);
         }
     } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
         // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
@@ -145,6 +83,36 @@
     }
 }
 
+void MultiTouchMotionAccumulator::syncSlots(const InputDeviceContext& deviceContext) {
+    if (!mUsingSlotsProtocol) {
+        return;
+    }
+    constexpr std::array<int32_t, 11> axisCodes = {ABS_MT_POSITION_X,  ABS_MT_POSITION_Y,
+                                                   ABS_MT_TOUCH_MAJOR, ABS_MT_TOUCH_MINOR,
+                                                   ABS_MT_WIDTH_MAJOR, ABS_MT_WIDTH_MINOR,
+                                                   ABS_MT_ORIENTATION, ABS_MT_TRACKING_ID,
+                                                   ABS_MT_PRESSURE,    ABS_MT_DISTANCE,
+                                                   ABS_MT_TOOL_TYPE};
+    const size_t numSlots = mSlots.size();
+    for (int32_t axisCode : axisCodes) {
+        if (!deviceContext.hasAbsoluteAxis(axisCode)) {
+            continue;
+        }
+        const auto result = deviceContext.getMtSlotValues(axisCode, numSlots);
+        if (result.ok()) {
+            const std::vector<int32_t>& mtSlotValues = result.value();
+            for (size_t i = 1; i <= numSlots; ++i) {
+                // The returned slot values are in a 1-indexed vector of size numSlots + 1.
+                mSlots[i - 1].populateAxisValue(axisCode, mtSlotValues[i]);
+            }
+        } else {
+            ALOGE("Could not retrieve multi-touch slot value for axis=%d error=%s status=%d",
+                  axisCode, result.error().message().c_str(), result.error().code().value());
+        }
+    }
+    populateCurrentSlot(deviceContext);
+}
+
 void MultiTouchMotionAccumulator::finishSync() {
     if (!mUsingSlotsProtocol) {
         resetSlots();
@@ -166,6 +134,21 @@
                          [](const Slot& slot) { return slot.mInUse; });
 }
 
+void MultiTouchMotionAccumulator::populateCurrentSlot(
+        const android::InputDeviceContext& deviceContext) {
+    if (!mUsingSlotsProtocol) {
+        return;
+    }
+    int32_t initialSlot;
+    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
+        status == OK) {
+        mCurrentSlot = initialSlot;
+    } else {
+        ALOGE("Could not retrieve current multi-touch slot index. status=%s",
+              statusToString(status).c_str());
+    }
+}
+
 // --- MultiTouchMotionAccumulator::Slot ---
 
 ToolType MultiTouchMotionAccumulator::Slot::getToolType() const {
@@ -182,4 +165,52 @@
     return ToolType::UNKNOWN;
 }
 
+void MultiTouchMotionAccumulator::Slot::populateAxisValue(int32_t axisCode, int32_t value) {
+    switch (axisCode) {
+        case ABS_MT_POSITION_X:
+            mAbsMtPositionX = value;
+            break;
+        case ABS_MT_POSITION_Y:
+            mAbsMtPositionY = value;
+            break;
+        case ABS_MT_TOUCH_MAJOR:
+            mAbsMtTouchMajor = value;
+            break;
+        case ABS_MT_TOUCH_MINOR:
+            mAbsMtTouchMinor = value;
+            mHaveAbsMtTouchMinor = true;
+            break;
+        case ABS_MT_WIDTH_MAJOR:
+            mAbsMtWidthMajor = value;
+            break;
+        case ABS_MT_WIDTH_MINOR:
+            mAbsMtWidthMinor = value;
+            mHaveAbsMtWidthMinor = true;
+            break;
+        case ABS_MT_ORIENTATION:
+            mAbsMtOrientation = value;
+            break;
+        case ABS_MT_TRACKING_ID:
+            if (value < 0) {
+                // The slot is no longer in use but it retains its previous contents,
+                // which may be reused for subsequent touches.
+                mInUse = false;
+            } else {
+                mInUse = true;
+                mAbsMtTrackingId = value;
+            }
+            break;
+        case ABS_MT_PRESSURE:
+            mAbsMtPressure = value;
+            break;
+        case ABS_MT_DISTANCE:
+            mAbsMtDistance = value;
+            break;
+        case ABS_MT_TOOL_TYPE:
+            mAbsMtToolType = value;
+            mHaveAbsMtToolType = true;
+            break;
+    }
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index 5b55e3d..a0f2147 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -68,12 +68,14 @@
         int32_t mAbsMtToolType = 0;
 
         void clear() { *this = Slot(); }
+        void populateAxisValue(int32_t axisCode, int32_t value);
     };
 
     MultiTouchMotionAccumulator();
 
     void configure(const InputDeviceContext& deviceContext, size_t slotCount,
                    bool usingSlotsProtocol);
+    void reset(const InputDeviceContext& deviceContext);
     void process(const RawEvent* rawEvent);
     void finishSync();
 
@@ -83,15 +85,16 @@
         LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index);
         return mSlots[index];
     }
-    void reset(const InputDeviceContext& deviceContext);
 
 private:
-    int32_t mCurrentSlot;
+    int32_t mCurrentSlot{-1};
     std::vector<Slot> mSlots;
     bool mUsingSlotsProtocol;
 
     void resetSlots();
+    void syncSlots(const InputDeviceContext& deviceContext);
     void warnIfNotInUse(const RawEvent& event, const Slot& slot);
+    void populateCurrentSlot(const android::InputDeviceContext& deviceContext);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 9552104..e8e7376 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -35,8 +35,14 @@
 
 namespace {
 
+// This will disable the tap to click while the user is typing on a physical keyboard
 const bool ENABLE_TOUCHPAD_PALM_REJECTION = input_flags::enable_touchpad_typing_palm_rejection();
 
+// In addition to v1, v2 will also cancel ongoing move gestures while typing and add delay in
+// re-enabling the tap to click.
+const bool ENABLE_TOUCHPAD_PALM_REJECTION_V2 =
+        input_flags::enable_v2_touchpad_typing_palm_rejection();
+
 uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) {
     switch (gesturesButton) {
         case GESTURES_BUTTON_LEFT:
@@ -60,8 +66,7 @@
                                    const InputDeviceContext& deviceContext, int32_t deviceId)
       : mDeviceId(deviceId),
         mReaderContext(readerContext),
-        mPointerController(readerContext.getPointerController(deviceId)),
-        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {
+        mEnableFlingStop(input_flags::enable_touchpad_fling_stop()) {
     deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo);
     deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
 }
@@ -75,6 +80,8 @@
     out << StringPrintf("Button state: 0x%08x\n", mButtonState);
     out << "Down time: " << mDownTime << "\n";
     out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n";
+    out << "Is hovering: " << mIsHovering << "\n";
+    out << "Enable Tap Timestamp: " << mWhenToEnableTapToClick << "\n";
     return out.str();
 }
 
@@ -82,7 +89,7 @@
     std::list<NotifyArgs> out;
     switch (mCurrentClassification) {
         case MotionClassification::TWO_FINGER_SWIPE:
-            out.push_back(endScroll(when, when));
+            out += endScroll(when, when);
             break;
         case MotionClassification::MULTI_FINGER_SWIPE:
             out += handleMultiFingerSwipeLift(when, when);
@@ -109,8 +116,6 @@
 void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const {
     info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0);
 
-    // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture
-    // is enabled.
     if (!mBoundsInLogicalDisplay.isEmpty()) {
         info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, mBoundsInLogicalDisplay.left,
                             mBoundsInLogicalDisplay.right, 0, 0, 0);
@@ -131,6 +136,7 @@
 }
 
 std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime,
+                                                      nsecs_t gestureStartTime,
                                                       const Gesture& gesture) {
     if (!mDisplayId) {
         // Ignore gestures when there is no target display configured.
@@ -139,13 +145,13 @@
 
     switch (gesture.type) {
         case kGestureTypeMove:
-            return {handleMove(when, readTime, gesture)};
+            return handleMove(when, readTime, gestureStartTime, gesture);
         case kGestureTypeButtonsChange:
             return handleButtonsChange(when, readTime, gesture);
         case kGestureTypeScroll:
             return handleScroll(when, readTime, gesture);
         case kGestureTypeFling:
-            return handleFling(when, readTime, gesture);
+            return handleFling(when, readTime, gestureStartTime, gesture);
         case kGestureTypeSwipe:
             return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx,
                                           gesture.details.swipe.dy);
@@ -162,56 +168,75 @@
     }
 }
 
-NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime,
-                                              const Gesture& gesture) {
+std::list<NotifyArgs> GestureConverter::handleMove(nsecs_t when, nsecs_t readTime,
+                                                   nsecs_t gestureStartTime,
+                                                   const Gesture& gesture) {
     float deltaX = gesture.details.move.dx;
     float deltaY = gesture.details.move.dy;
-    if (ENABLE_TOUCHPAD_PALM_REJECTION && (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) {
-        enableTapToClick();
+    if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) {
+        bool wasHoverCancelled = mIsHoverCancelled;
+        // Gesture will be cancelled if it started before the user started typing and
+        // there is a active IME connection.
+        mIsHoverCancelled = gestureStartTime <= mReaderContext.getLastKeyDownTimestamp() &&
+                mReaderContext.getPolicy()->isInputMethodConnectionActive();
+
+        if (!wasHoverCancelled && mIsHoverCancelled) {
+            // This is the first event of the cancelled gesture, we won't return because we need to
+            // generate a HOVER_EXIT event
+            return exitHover(when, readTime);
+        } else if (mIsHoverCancelled) {
+            return {};
+        }
     }
+
     rotateDelta(mOrientation, &deltaX, &deltaY);
 
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    mPointerController->move(deltaX, deltaY);
-    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    // Update the cursor, and enable tap to click if the gesture is not cancelled
+    if (!mIsHoverCancelled) {
+        // handleFling calls hoverMove with zero delta on FLING_TAP_DOWN. Don't enable tap to click
+        // for this case as subsequent handleButtonsChange may choose to ignore this tap.
+        if ((ENABLE_TOUCHPAD_PALM_REJECTION || ENABLE_TOUCHPAD_PALM_REJECTION_V2) &&
+            (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) {
+            enableTapToClick(when);
+        }
+    }
 
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
+    std::list<NotifyArgs> out;
+    const bool down = isPointerDown(mButtonState);
+    if (!down) {
+        out += enterHover(when, readTime);
+    }
 
     PointerCoords coords;
     coords.clear();
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
-    const bool down = isPointerDown(mButtonState);
     coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
 
     const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
-    return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
-                          /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition,
-                          yCursorPosition);
+    out.push_back(makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState,
+                                 /*pointerCount=*/1, &coords));
+    return out;
 }
 
 std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime,
                                                             const Gesture& gesture) {
     std::list<NotifyArgs> out = {};
 
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
-
     PointerCoords coords;
     coords.clear();
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
 
-    if (ENABLE_TOUCHPAD_PALM_REJECTION && mReaderContext.isPreventingTouchpadTaps()) {
-        enableTapToClick();
+    // V2 palm rejection should override V1
+    if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) {
+        enableTapToClick(when);
+        if (gesture.details.buttons.is_tap && when <= mWhenToEnableTapToClick) {
+            // return early to prevent this tap
+            return out;
+        }
+    } else if (ENABLE_TOUCHPAD_PALM_REJECTION && mReaderContext.isPreventingTouchpadTaps()) {
+        enableTapToClick(when);
         if (gesture.details.buttons.is_tap) {
             // return early to prevent this tap
             return out;
@@ -232,16 +257,15 @@
             newButtonState |= actionButton;
             pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
                                                  actionButton, newButtonState,
-                                                 /* pointerCount= */ 1, mFingerProps.data(),
-                                                 &coords, xCursorPosition, yCursorPosition));
+                                                 /*pointerCount=*/1, &coords));
         }
     }
     if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
         mDownTime = when;
+        out += exitHover(when, readTime);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), &coords, xCursorPosition,
-                                     yCursorPosition));
+                                     &coords));
     }
     out.splice(out.end(), pressEvents);
 
@@ -257,20 +281,15 @@
             newButtonState &= ~actionButton;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                                          actionButton, newButtonState, /* pointerCount= */ 1,
-                                         mFingerProps.data(), &coords, xCursorPosition,
-                                         yCursorPosition));
+                                         &coords));
         }
     }
     if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                                     newButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                                     &coords, xCursorPosition, yCursorPosition));
-        // Send a HOVER_MOVE to tell the application that the mouse is hovering again.
-        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_HOVER_MOVE,
-                                     /*actionButton=*/0, newButtonState, /*pointerCount=*/1,
-                                     mFingerProps.data(), &coords, xCursorPosition,
-                                     yCursorPosition));
+                                     newButtonState, /* pointerCount= */ 1, &coords));
+        mButtonState = newButtonState;
+        out += enterHover(when, readTime);
     }
     mButtonState = newButtonState;
     return out;
@@ -278,13 +297,9 @@
 
 std::list<NotifyArgs> GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
     const bool pointerDown = isPointerDown(mButtonState);
@@ -295,18 +310,16 @@
         if (mButtonState & button) {
             newButtonState &= ~button;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                                         button, newButtonState, /*pointerCount=*/1,
-                                         mFingerProps.data(), &coords, xCursorPosition,
-                                         yCursorPosition));
+                                         button, newButtonState, /*pointerCount=*/1, &coords));
         }
     }
+    mButtonState = 0;
     if (pointerDown) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
-                                     newButtonState, /*pointerCount=*/1, mFingerProps.data(),
-                                     &coords, xCursorPosition, yCursorPosition));
+                                     mButtonState, /*pointerCount=*/1, &coords));
+        out += enterHover(when, readTime);
     }
-    mButtonState = 0;
     return out;
 }
 
@@ -314,18 +327,16 @@
                                                      const Gesture& gesture) {
     std::list<NotifyArgs> out;
     PointerCoords& coords = mFakeFingerCoords[0];
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+        out += exitHover(when, readTime);
+
         mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
-        coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-        coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        coords.clear();
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         mDownTime = when;
         NotifyMotionArgs args =
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0,
-                               mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                               mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                               mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
         args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
         out.push_back(args);
     }
@@ -340,14 +351,14 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy);
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
-                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
     args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
     out.push_back(args);
     return out;
 }
 
 std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTime,
+                                                    nsecs_t gestureStartTime,
                                                     const Gesture& gesture) {
     switch (gesture.details.fling.fling_state) {
         case GESTURES_FLING_START:
@@ -356,23 +367,49 @@
                 // ensure consistency between touchscreen and touchpad flings), so we're just using
                 // the "start fling" gestures as a marker for the end of a two-finger scroll
                 // gesture.
-                return {endScroll(when, readTime)};
+                mFlingMayBeInProgress = true;
+                return endScroll(when, readTime);
             }
             break;
         case GESTURES_FLING_TAP_DOWN:
             if (mCurrentClassification == MotionClassification::NONE) {
-                // Use the tap down state of a fling gesture as an indicator that a contact
-                // has been initiated with the touchpad. We treat this as a move event with zero
-                // magnitude, which will also result in the pointer icon being updated.
-                // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been
-                //  initiated with a touchpad.
-                if (!mReaderContext.isPreventingTouchpadTaps()) {
-                    enableTapToClick();
+                if (mEnableFlingStop && mFlingMayBeInProgress) {
+                    // The user has just touched the pad again after ending a two-finger scroll
+                    // motion, which might have started a fling. We want to stop the fling, but
+                    // unfortunately there's currently no API for doing so. Instead, send and
+                    // immediately cancel a fake finger to cause the scrolling to stop and hopefully
+                    // avoid side effects (e.g. activation of UI elements).
+                    // TODO(b/326056750): add an API for fling stops.
+                    mFlingMayBeInProgress = false;
+                    PointerCoords coords;
+                    coords.clear();
+                    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+                    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+
+                    std::list<NotifyArgs> out;
+                    mDownTime = when;
+                    mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
+                    out += exitHover(when, readTime);
+                    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+                                                 /*actionButton=*/0, /*buttonState=*/0,
+                                                 /*pointerCount=*/1, &coords));
+                    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_CANCEL,
+                                                 /*actionButton=*/0, /*buttonState=*/0,
+                                                 /*pointerCount=*/1, &coords));
+                    out += enterHover(when, readTime);
+                    mCurrentClassification = MotionClassification::NONE;
+                    return out;
+                } else {
+                    // Use the tap down state of a fling gesture as an indicator that a contact
+                    // has been initiated with the touchpad. We treat this as a move event with zero
+                    // magnitude, which will also result in the pointer icon being updated.
+                    // TODO(b/282023644): Add a signal in libgestures for when a stable contact has
+                    //  been initiated with a touchpad.
+                    return handleMove(when, readTime, gestureStartTime,
+                                      Gesture(kGestureMove, gesture.start_time, gesture.end_time,
+                                              /*dx=*/0.f,
+                                              /*dy=*/0.f));
                 }
-                return {handleMove(when, readTime,
-                                   Gesture(kGestureMove, gesture.start_time, gesture.end_time,
-                                           /*dx=*/0.f,
-                                           /*dy=*/0.f))};
             }
             break;
         default:
@@ -382,18 +419,18 @@
     return {};
 }
 
-NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
+std::list<NotifyArgs> GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
+    std::list<NotifyArgs> out;
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
     args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+    out.push_back(args);
     mCurrentClassification = MotionClassification::NONE;
-    return args;
+    out += enterHover(when, readTime);
+    return out;
 }
 
 [[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when,
@@ -402,23 +439,26 @@
                                                                              float dx, float dy) {
     std::list<NotifyArgs> out = {};
 
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         // If the user changes the number of fingers mid-way through a swipe (e.g. they start with
         // three and then put a fourth finger down), the gesture library will treat it as two
         // separate swipes with an appropriate lift event between them, so we don't have to worry
         // about the finger count changing mid-swipe.
+
+        out += exitHover(when, readTime);
+
         mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE;
+
         mSwipeFingerCount = fingerCount;
 
         constexpr float FAKE_FINGER_SPACING = 100;
-        float xCoord = xCursorPosition - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2;
+        float xCoord = 0.f - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2;
         for (size_t i = 0; i < mSwipeFingerCount; i++) {
             PointerCoords& coords = mFakeFingerCoords[i];
             coords.clear();
+            // PointerChoreographer will add the cursor position to these pointers.
             coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCoord);
-            coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+            coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
             coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
             xCoord += FAKE_FINGER_SPACING;
         }
@@ -428,16 +468,13 @@
                                           fingerCount);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data()));
         for (size_t i = 1; i < mSwipeFingerCount; i++) {
             out.push_back(makeMotionArgs(when, readTime,
                                          AMOTION_EVENT_ACTION_POINTER_DOWN |
                                                  (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                          /* actionButton= */ 0, mButtonState,
-                                         /* pointerCount= */ i + 1, mFingerProps.data(),
-                                         mFakeFingerCoords.data(), xCursorPosition,
-                                         yCursorPosition));
+                                         /* pointerCount= */ i + 1, mFakeFingerCoords.data()));
         }
     }
     float rotatedDeltaX = dx, rotatedDeltaY = -dy;
@@ -455,8 +492,7 @@
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
                                  mButtonState, /* pointerCount= */ mSwipeFingerCount,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
+                                 mFakeFingerCoords.data()));
     return out;
 }
 
@@ -466,8 +502,6 @@
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         return out;
     }
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0);
 
@@ -476,24 +510,20 @@
                                      AMOTION_EVENT_ACTION_POINTER_UP |
                                              ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ i,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data()));
     }
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
                                  /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
+                                 mFakeFingerCoords.data()));
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT, 0);
     mCurrentClassification = MotionClassification::NONE;
+    out += enterHover(when, readTime);
     mSwipeFingerCount = 0;
     return out;
 }
 
 [[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
                                                                   const Gesture& gesture) {
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
-
     // Pinch gesture phases are reported a little differently from others, in that the same details
     // struct is used for all phases of the gesture, just with different zoom_state values. When
     // zoom_state is START or END, dz will always be 1, so we don't need to move the pointers in
@@ -503,29 +533,29 @@
         LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START,
                             "First pinch gesture does not have the START zoom state (%d instead).",
                             gesture.details.pinch.zoom_state);
+        std::list<NotifyArgs> out;
+
+        out += exitHover(when, readTime);
+
         mCurrentClassification = MotionClassification::PINCH;
         mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX;
+        // PointerChoreographer will add the cursor position to these pointers.
         mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
-        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                          xCursorPosition - mPinchFingerSeparation / 2);
-        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f - mPinchFingerSeparation / 2);
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
         mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
-        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                          xCursorPosition + mPinchFingerSeparation / 2);
-        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f + mPinchFingerSeparation / 2);
+        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
         mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         mDownTime = when;
-        std::list<NotifyArgs> out;
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data()));
         out.push_back(makeMotionArgs(when, readTime,
                                      AMOTION_EVENT_ACTION_POINTER_DOWN |
                                              1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data()));
         return out;
     }
 
@@ -534,45 +564,65 @@
     }
 
     mPinchFingerSeparation *= gesture.details.pinch.dz;
+    // PointerChoreographer will add the cursor position to these pointers.
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR,
                                       gesture.details.pinch.dz);
-    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                      xCursorPosition - mPinchFingerSeparation / 2);
-    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
-    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                      xCursorPosition + mPinchFingerSeparation / 2);
-    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f - mPinchFingerSeparation / 2);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
+    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f + mPinchFingerSeparation / 2);
+    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
     return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0,
-                           mButtonState, /*pointerCount=*/2, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)};
+                           mButtonState, /*pointerCount=*/2, mFakeFingerCoords.data())};
 }
 
 std::list<NotifyArgs> GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    const auto [xCursorPosition, yCursorPosition] =
-            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
     out.push_back(makeMotionArgs(when, readTime,
                                  AMOTION_EVENT_ACTION_POINTER_UP |
                                          1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
                                  /*actionButton=*/0, mButtonState, /*pointerCount=*/2,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
+                                 mFakeFingerCoords.data()));
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
-                                 mButtonState, /*pointerCount=*/1, mFingerProps.data(),
-                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
-    mCurrentClassification = MotionClassification::NONE;
+                                 mButtonState, /*pointerCount=*/1, mFakeFingerCoords.data()));
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
+    mCurrentClassification = MotionClassification::NONE;
+    out += enterHover(when, readTime);
     return out;
 }
 
+std::list<NotifyArgs> GestureConverter::enterHover(nsecs_t when, nsecs_t readTime) {
+    if (!mIsHovering) {
+        mIsHovering = true;
+        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_ENTER)};
+    } else {
+        return {};
+    }
+}
+
+std::list<NotifyArgs> GestureConverter::exitHover(nsecs_t when, nsecs_t readTime) {
+    if (mIsHovering) {
+        mIsHovering = false;
+        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_EXIT)};
+    } else {
+        return {};
+    }
+}
+
+NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action) {
+    PointerCoords coords;
+    coords.clear();
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+    return makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState,
+                          /*pointerCount=*/1, &coords);
+}
+
 NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                                   int32_t actionButton, int32_t buttonState,
                                                   uint32_t pointerCount,
-                                                  const PointerProperties* pointerProperties,
-                                                  const PointerCoords* pointerCoords,
-                                                  float xCursorPosition, float yCursorPosition) {
+                                                  const PointerCoords* pointerCoords) {
     return {mReaderContext.getNextId(),
             when,
             readTime,
@@ -588,18 +638,21 @@
             mCurrentClassification,
             AMOTION_EVENT_EDGE_FLAG_NONE,
             pointerCount,
-            pointerProperties,
+            mFingerProps.data(),
             pointerCoords,
             /* xPrecision= */ 1.0f,
             /* yPrecision= */ 1.0f,
-            xCursorPosition,
-            yCursorPosition,
+            /* xCursorPosition= */ 0.f,
+            /* yCursorPosition= */ 0.f,
             /* downTime= */ mDownTime,
             /* videoFrames= */ {}};
 }
 
-void GestureConverter::enableTapToClick() {
-    mReaderContext.setPreventingTouchpadTaps(false);
+void GestureConverter::enableTapToClick(nsecs_t when) {
+    if (mReaderContext.isPreventingTouchpadTaps()) {
+        mWhenToEnableTapToClick = when + TAP_ENABLE_DELAY_NANOS.count();
+        mReaderContext.setPreventingTouchpadTaps(false);
+    }
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 732a4b2..829fb92 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -20,7 +20,6 @@
 #include <list>
 #include <memory>
 
-#include <PointerControllerInterface.h>
 #include <android/input.h>
 #include <utils/Timers.h>
 
@@ -34,8 +33,14 @@
 
 namespace android {
 
-// Converts Gesture structs from the gestures library into NotifyArgs and the appropriate
-// PointerController calls.
+using std::chrono_literals::operator""ms;
+/**
+ * This duration is decided based on internal team testing, it may be updated after testing with
+ * larger groups
+ */
+constexpr std::chrono::nanoseconds TAP_ENABLE_DELAY_NANOS = 400ms;
+
+// Converts Gesture structs from the gestures library into NotifyArgs.
 class GestureConverter {
 public:
     GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
@@ -46,26 +51,29 @@
     void setOrientation(ui::Rotation orientation) { mOrientation = orientation; }
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when);
 
-    void setDisplayId(std::optional<int32_t> displayId) { mDisplayId = displayId; }
+    void setDisplayId(std::optional<ui::LogicalDisplayId> displayId) { mDisplayId = displayId; }
 
     void setBoundsInLogicalDisplay(FloatRect bounds) { mBoundsInLogicalDisplay = bounds; }
 
     void populateMotionRanges(InputDeviceInfo& info) const;
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
+                                                      nsecs_t gestureStartTime,
                                                       const Gesture& gesture);
 
 private:
-    [[nodiscard]] NotifyMotionArgs handleMove(nsecs_t when, nsecs_t readTime,
-                                              const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> handleMove(nsecs_t when, nsecs_t readTime,
+                                                   nsecs_t gestureStartTime,
+                                                   const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
                                                             const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> releaseAllButtons(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
                                                      const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleFling(nsecs_t when, nsecs_t readTime,
+                                                    nsecs_t gestureStartTime,
                                                     const Gesture& gesture);
-    [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> endScroll(nsecs_t when, nsecs_t readTime);
 
     [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
                                                                uint32_t fingerCount, float dx,
@@ -75,21 +83,24 @@
                                                     const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> endPinch(nsecs_t when, nsecs_t readTime);
 
+    [[nodiscard]] std::list<NotifyArgs> enterHover(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> exitHover(nsecs_t when, nsecs_t readTime);
+
+    NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action);
+
     NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                     int32_t actionButton, int32_t buttonState,
-                                    uint32_t pointerCount,
-                                    const PointerProperties* pointerProperties,
-                                    const PointerCoords* pointerCoords, float xCursorPosition,
-                                    float yCursorPosition);
+                                    uint32_t pointerCount, const PointerCoords* pointerCoords);
 
-    void enableTapToClick();
+    void enableTapToClick(nsecs_t when);
+    bool mIsHoverCancelled{false};
+    nsecs_t mWhenToEnableTapToClick{0};
 
     const int32_t mDeviceId;
     InputReaderContext& mReaderContext;
-    std::shared_ptr<PointerControllerInterface> mPointerController;
-    const bool mEnablePointerChoreographer;
+    const bool mEnableFlingStop;
 
-    std::optional<int32_t> mDisplayId;
+    std::optional<ui::LogicalDisplayId> mDisplayId;
     FloatRect mBoundsInLogicalDisplay{};
     ui::Rotation mOrientation = ui::ROTATION_0;
     RawAbsoluteAxisInfo mXAxisInfo;
@@ -99,6 +110,12 @@
     // button values (AMOTION_EVENT_BUTTON_...).
     uint32_t mButtonState = 0;
     nsecs_t mDownTime = 0;
+    // Whether we are currently in a hover state (i.e. a HOVER_ENTER event has been sent without a
+    // matching HOVER_EXIT).
+    bool mIsHovering = false;
+    // Whether we've received a "fling start" gesture (i.e. the end of a scroll) but no "fling tap
+    // down" gesture to match it yet.
+    bool mFlingMayBeInProgress = false;
 
     MotionClassification mCurrentClassification = MotionClassification::NONE;
     // Only used when mCurrentClassification is MULTI_FINGER_SWIPE.
diff --git a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
index 81b4968..26028c5 100644
--- a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
@@ -26,15 +26,30 @@
 
 extern "C" {
 
+namespace {
+
+/**
+ * Log details of each gesture output by the gestures library.
+ * Enable this via "adb shell setprop log.tag.TouchpadInputMapperGestures DEBUG" (requires
+ * restarting the shell)
+ */
+const bool DEBUG_TOUCHPAD_GESTURES =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, "TouchpadInputMapperGestures",
+                                  ANDROID_LOG_INFO);
+
+} // namespace
+
 void gestures_log(int verb, const char* fmt, ...) {
     va_list args;
     va_start(args, fmt);
     if (verb == GESTURES_LOG_ERROR) {
         LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
-    } else if (verb == GESTURES_LOG_INFO) {
-        LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
-    } else {
-        LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args);
+    } else if (DEBUG_TOUCHPAD_GESTURES) {
+        if (verb == GESTURES_LOG_INFO) {
+            LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
+        } else {
+            LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args);
+        }
     }
     va_end(args);
 }
diff --git a/services/inputflinger/reporter/Android.bp b/services/inputflinger/reporter/Android.bp
index b1e1aee..e85a104 100644
--- a/services/inputflinger/reporter/Android.bp
+++ b/services/inputflinger/reporter/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
index 2803805..255c7eb 100644
--- a/services/inputflinger/rust/Android.bp
+++ b/services/inputflinger/rust/Android.bp
@@ -13,6 +13,10 @@
 // limitations under the License.
 
 // Generate the C++ code that Rust calls into.
+package {
+    default_team: "trendy_team_input_framework",
+}
+
 genrule {
     name: "inputflinger_rs_bootstrap_bridge_code",
     tools: ["cxxbridge"],
@@ -42,6 +46,7 @@
         "libbinder_rs",
         "liblog_rust",
         "liblogger",
+        "libnix",
     ],
     host_supported: true,
 }
diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs
index 894b881..2d5039a 100644
--- a/services/inputflinger/rust/bounce_keys_filter.rs
+++ b/services/inputflinger/rust/bounce_keys_filter.rs
@@ -118,6 +118,10 @@
         }
         self.next.notify_devices_changed(device_infos);
     }
+
+    fn destroy(&mut self) {
+        self.next.destroy();
+    }
 }
 
 #[cfg(test)]
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index 340ff8e..6df339e 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -22,11 +22,15 @@
 use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
     DeviceInfo::DeviceInfo,
     IInputFilter::{IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks},
+    IInputThread::{IInputThread, IInputThreadCallback::IInputThreadCallback},
     InputFilterConfiguration::InputFilterConfiguration,
     KeyEvent::KeyEvent,
 };
 
 use crate::bounce_keys_filter::BounceKeysFilter;
+use crate::input_filter_thread::InputFilterThread;
+use crate::slow_keys_filter::SlowKeysFilter;
+use crate::sticky_keys_filter::StickyKeysFilter;
 use log::{error, info};
 use std::sync::{Arc, Mutex, RwLock};
 
@@ -34,6 +38,7 @@
 pub trait Filter {
     fn notify_key(&mut self, event: &KeyEvent);
     fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]);
+    fn destroy(&mut self);
 }
 
 struct InputFilterState {
@@ -49,6 +54,7 @@
     // Access to mutable references to mutable state (includes access to filters, enabled, etc.) is
     // guarded by Mutex for thread safety
     state: Mutex<InputFilterState>,
+    input_filter_thread: InputFilterThread,
 }
 
 impl Interface for InputFilter {}
@@ -66,7 +72,11 @@
         first_filter: Box<dyn Filter + Send + Sync>,
         callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
     ) -> InputFilter {
-        Self { callbacks, state: Mutex::new(InputFilterState { first_filter, enabled: false }) }
+        Self {
+            callbacks: callbacks.clone(),
+            state: Mutex::new(InputFilterState { first_filter, enabled: false }),
+            input_filter_thread: InputFilterThread::new(InputFilterThreadCreator::new(callbacks)),
+        }
     }
 }
 
@@ -88,16 +98,36 @@
     }
 
     fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> {
-        let mut state = self.state.lock().unwrap();
-        let mut first_filter: Box<dyn Filter + Send + Sync> =
-            Box::new(BaseFilter::new(self.callbacks.clone()));
-        if config.bounceKeysThresholdNs > 0 {
-            first_filter =
-                Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
-            state.enabled = true;
-            info!("Bounce keys filter is installed");
+        {
+            let mut state = self.state.lock().unwrap();
+            state.first_filter.destroy();
+            let mut first_filter: Box<dyn Filter + Send + Sync> =
+                Box::new(BaseFilter::new(self.callbacks.clone()));
+            if config.stickyKeysEnabled {
+                first_filter = Box::new(StickyKeysFilter::new(
+                    first_filter,
+                    ModifierStateListener::new(self.callbacks.clone()),
+                ));
+                state.enabled = true;
+                info!("Sticky keys filter is installed");
+            }
+            if config.slowKeysThresholdNs > 0 {
+                first_filter = Box::new(SlowKeysFilter::new(
+                    first_filter,
+                    config.slowKeysThresholdNs,
+                    self.input_filter_thread.clone(),
+                ));
+                state.enabled = true;
+                info!("Slow keys filter is installed");
+            }
+            if config.bounceKeysThresholdNs > 0 {
+                first_filter =
+                    Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
+                state.enabled = true;
+                info!("Bounce keys filter is installed");
+            }
+            state.first_filter = first_filter;
         }
-        state.first_filter = first_filter;
         Result::Ok(())
     }
 }
@@ -123,36 +153,69 @@
     fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
         // do nothing
     }
+
+    fn destroy(&mut self) {
+        // do nothing
+    }
+}
+
+/// This struct wraps around IInputFilterCallbacks restricting access to only
+/// {@code onModifierStateChanged()} method of the callback.
+#[derive(Clone)]
+pub struct ModifierStateListener(Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>);
+
+impl ModifierStateListener {
+    pub fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> ModifierStateListener {
+        Self(callbacks)
+    }
+
+    pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) {
+        let _ = self
+            .0
+            .read()
+            .unwrap()
+            .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32);
+    }
+}
+
+/// This struct wraps around IInputFilterCallbacks restricting access to only
+/// {@code createInputFilterThread()} method of the callback.
+#[derive(Clone)]
+pub struct InputFilterThreadCreator(Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>);
+
+impl InputFilterThreadCreator {
+    pub fn new(
+        callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+    ) -> InputFilterThreadCreator {
+        Self(callbacks)
+    }
+
+    pub fn create(
+        &self,
+        input_thread_callback: &Strong<dyn IInputThreadCallback>,
+    ) -> Strong<dyn IInputThread> {
+        self.0.read().unwrap().createInputFilterThread(input_thread_callback).unwrap()
+    }
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::input_filter::{test_filter::TestFilter, Filter, InputFilter};
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, InputFilter,
+    };
     use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
-    use binder::{Interface, Strong};
+    use binder::Strong;
     use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
         DeviceInfo::DeviceInfo, IInputFilter::IInputFilter,
-        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
         InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent,
         KeyEventAction::KeyEventAction,
     };
     use std::sync::{Arc, RwLock};
 
-    struct FakeCallbacks {}
-
-    impl Interface for FakeCallbacks {}
-
-    impl IInputFilterCallbacks for FakeCallbacks {
-        fn sendKeyEvent(&self, _event: &KeyEvent) -> binder::Result<()> {
-            Result::Ok(())
-        }
-    }
-
     #[test]
     fn test_not_enabled_with_default_filter() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
         let result = input_filter.isEnabled();
         assert!(result.is_ok());
         assert!(!result.unwrap());
@@ -160,17 +223,21 @@
 
     #[test]
     fn test_notify_key_with_no_filters() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks.clone())));
         let event = create_key_event();
         assert!(input_filter.notifyKey(&event).is_ok());
+        assert_eq!(test_callbacks.last_event().unwrap(), event);
     }
 
     #[test]
     fn test_notify_key_with_filter() {
         let test_filter = TestFilter::new();
-        let input_filter = create_input_filter(Box::new(test_filter.clone()));
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
         let event = create_key_event();
         assert!(input_filter.notifyKey(&event).is_ok());
         assert_eq!(test_filter.last_event().unwrap(), event);
@@ -179,7 +246,11 @@
     #[test]
     fn test_notify_devices_changed() {
         let test_filter = TestFilter::new();
-        let input_filter = create_input_filter(Box::new(test_filter.clone()));
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
         assert!(input_filter
             .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }])
             .is_ok());
@@ -188,21 +259,57 @@
 
     #[test]
     fn test_notify_configuration_changed_enabled_bounce_keys() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
-        let result = input_filter
-            .notifyConfigurationChanged(&InputFilterConfiguration { bounceKeysThresholdNs: 100 });
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            bounceKeysThresholdNs: 100,
+            ..Default::default()
+        });
         assert!(result.is_ok());
         let result = input_filter.isEnabled();
         assert!(result.is_ok());
         assert!(result.unwrap());
     }
 
-    fn create_input_filter(filter: Box<dyn Filter + Send + Sync>) -> InputFilter {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        InputFilter::create_input_filter(filter, Arc::new(RwLock::new(fake_callbacks)))
+    #[test]
+    fn test_notify_configuration_changed_enabled_sticky_keys() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            stickyKeysEnabled: true,
+            ..Default::default()
+        });
+        assert!(result.is_ok());
+        let result = input_filter.isEnabled();
+        assert!(result.is_ok());
+        assert!(result.unwrap());
+    }
+
+    #[test]
+    fn test_notify_configuration_changed_enabled_slow_keys() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            slowKeysThresholdNs: 100,
+            ..Default::default()
+        });
+        assert!(result.is_ok());
+        let result = input_filter.isEnabled();
+        assert!(result.is_ok());
+        assert!(result.unwrap());
+    }
+
+    #[test]
+    fn test_notify_configuration_changed_destroys_existing_filters() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
+        let _ = input_filter
+            .notifyConfigurationChanged(&InputFilterConfiguration { ..Default::default() });
+        assert!(test_filter.is_destroy_called());
     }
 
     fn create_key_event() -> KeyEvent {
@@ -236,6 +343,7 @@
     struct TestFilterInner {
         is_device_changed_called: bool,
         last_event: Option<KeyEvent>,
+        is_destroy_called: bool,
     }
 
     #[derive(Default, Clone)]
@@ -261,6 +369,10 @@
         pub fn is_device_changed_called(&self) -> bool {
             self.0.read().unwrap().is_device_changed_called
         }
+
+        pub fn is_destroy_called(&self) -> bool {
+            self.0.read().unwrap().is_destroy_called
+        }
     }
 
     impl Filter for TestFilter {
@@ -270,5 +382,186 @@
         fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
             self.inner().is_device_changed_called = true;
         }
+        fn destroy(&mut self) {
+            self.inner().is_destroy_called = true;
+        }
+    }
+}
+
+#[cfg(test)]
+pub mod test_callbacks {
+    use binder::{BinderFeatures, Interface, Strong};
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+        IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback},
+        KeyEvent::KeyEvent,
+    };
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, RwLock, RwLockWriteGuard};
+    use std::time::Duration;
+
+    #[derive(Default)]
+    struct TestCallbacksInner {
+        last_modifier_state: u32,
+        last_locked_modifier_state: u32,
+        last_event: Option<KeyEvent>,
+        test_thread: Option<FakeCppThread>,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestCallbacks(Arc<RwLock<TestCallbacksInner>>);
+
+    impl Interface for TestCallbacks {}
+
+    impl TestCallbacks {
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, TestCallbacksInner> {
+            self.0.write().unwrap()
+        }
+
+        pub fn last_event(&self) -> Option<KeyEvent> {
+            self.0.read().unwrap().last_event
+        }
+
+        pub fn clear(&mut self) {
+            self.inner().last_event = None;
+            self.inner().last_modifier_state = 0;
+            self.inner().last_locked_modifier_state = 0;
+        }
+
+        pub fn get_last_modifier_state(&self) -> u32 {
+            self.0.read().unwrap().last_modifier_state
+        }
+
+        pub fn get_last_locked_modifier_state(&self) -> u32 {
+            self.0.read().unwrap().last_locked_modifier_state
+        }
+
+        pub fn is_thread_running(&self) -> bool {
+            if let Some(test_thread) = &self.0.read().unwrap().test_thread {
+                return test_thread.is_running();
+            }
+            false
+        }
+    }
+
+    impl IInputFilterCallbacks for TestCallbacks {
+        fn sendKeyEvent(&self, event: &KeyEvent) -> binder::Result<()> {
+            self.inner().last_event = Some(*event);
+            Result::Ok(())
+        }
+
+        fn onModifierStateChanged(
+            &self,
+            modifier_state: i32,
+            locked_modifier_state: i32,
+        ) -> std::result::Result<(), binder::Status> {
+            self.inner().last_modifier_state = modifier_state as u32;
+            self.inner().last_locked_modifier_state = locked_modifier_state as u32;
+            Result::Ok(())
+        }
+
+        fn createInputFilterThread(
+            &self,
+            callback: &Strong<dyn IInputThreadCallback>,
+        ) -> std::result::Result<Strong<dyn IInputThread>, binder::Status> {
+            let test_thread = FakeCppThread::new(callback.clone());
+            test_thread.start_looper();
+            self.inner().test_thread = Some(test_thread.clone());
+            Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default()))
+        }
+    }
+
+    #[derive(Default)]
+    struct FakeCppThreadInner {
+        join_handle: Option<std::thread::JoinHandle<()>>,
+    }
+
+    #[derive(Clone)]
+    struct FakeCppThread {
+        callback: Arc<RwLock<Strong<dyn IInputThreadCallback>>>,
+        inner: Arc<RwLock<FakeCppThreadInner>>,
+        exit_flag: Arc<AtomicBool>,
+    }
+
+    impl Interface for FakeCppThread {}
+
+    impl FakeCppThread {
+        pub fn new(callback: Strong<dyn IInputThreadCallback>) -> Self {
+            let thread = Self {
+                callback: Arc::new(RwLock::new(callback)),
+                inner: Arc::new(RwLock::new(FakeCppThreadInner { join_handle: None })),
+                exit_flag: Arc::new(AtomicBool::new(true)),
+            };
+            thread.create_looper();
+            thread
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, FakeCppThreadInner> {
+            self.inner.write().unwrap()
+        }
+
+        fn create_looper(&self) {
+            let clone = self.clone();
+            let join_handle = std::thread::Builder::new()
+                .name("fake_cpp_thread".to_string())
+                .spawn(move || loop {
+                    if !clone.exit_flag.load(Ordering::Relaxed) {
+                        clone.loop_once();
+                    }
+                })
+                .unwrap();
+            self.inner().join_handle = Some(join_handle);
+            // Sleep until the looper thread starts
+            std::thread::sleep(Duration::from_millis(10));
+        }
+
+        pub fn start_looper(&self) {
+            self.exit_flag.store(false, Ordering::Relaxed);
+        }
+
+        pub fn stop_looper(&self) {
+            self.exit_flag.store(true, Ordering::Relaxed);
+            if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+                join_handle.thread().unpark();
+            }
+        }
+
+        pub fn is_running(&self) -> bool {
+            !self.exit_flag.load(Ordering::Relaxed)
+        }
+
+        fn loop_once(&self) {
+            let _ = self.callback.read().unwrap().loopOnce();
+        }
+    }
+
+    impl IInputThread for FakeCppThread {
+        fn finish(&self) -> binder::Result<()> {
+            self.stop_looper();
+            Result::Ok(())
+        }
+
+        fn wake(&self) -> binder::Result<()> {
+            if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+                join_handle.thread().unpark();
+            }
+            Result::Ok(())
+        }
+
+        fn sleepUntil(&self, wake_up_time: i64) -> binder::Result<()> {
+            let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+            if wake_up_time == i64::MAX {
+                std::thread::park();
+            } else {
+                let duration_now = Duration::from_nanos(now as u64);
+                let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
+                std::thread::park_timeout(duration_wake_up - duration_now);
+            }
+            Result::Ok(())
+        }
     }
 }
diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs
new file mode 100644
index 0000000..34f9b25
--- /dev/null
+++ b/services/inputflinger/rust/input_filter_thread.rs
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Input filter thread implementation in rust.
+//! Using IInputFilter.aidl interface to create ever looping thread with JNI support, rest of
+//! thread handling is done from rust side.
+//!
+//! NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
+//! have JNI support and can't call into Java policy that we use currently. libutils provided
+//! Thread.h also recommends against using std::thread and using the provided infrastructure that
+//! already provides way of attaching JniEnv to the created thread. So, we are using an AIDL
+//! interface to expose the InputThread infrastructure to rust.
+
+use crate::input_filter::InputFilterThreadCreator;
+use binder::{BinderFeatures, Interface, Strong};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::IInputThread::{
+    IInputThread, IInputThreadCallback::BnInputThreadCallback,
+    IInputThreadCallback::IInputThreadCallback,
+};
+use log::{debug, error};
+use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+/// Interface to receive callback from Input filter thread
+pub trait ThreadCallback {
+    /// Calls back after the requested timeout expires.
+    /// {@see InputFilterThread.request_timeout_at_time(...)}
+    ///
+    /// NOTE: In case of multiple requests, the timeout request which is earliest in time, will be
+    /// fulfilled and notified to all the listeners. It's up to the listeners to re-request another
+    /// timeout in the future.
+    fn notify_timeout_expired(&self, when_nanos: i64);
+    /// Unique name for the listener, which will be used to uniquely identify the listener.
+    fn name(&self) -> &str;
+}
+
+#[derive(Clone)]
+pub struct InputFilterThread {
+    thread_creator: InputFilterThreadCreator,
+    thread_callback_handler: ThreadCallbackHandler,
+    inner: Arc<RwLock<InputFilterThreadInner>>,
+    looper: Arc<RwLock<Looper>>,
+}
+
+struct InputFilterThreadInner {
+    next_timeout: i64,
+    is_finishing: bool,
+}
+
+struct Looper {
+    cpp_thread: Option<Strong<dyn IInputThread>>,
+}
+
+impl InputFilterThread {
+    /// Create a new InputFilterThread instance.
+    /// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread.
+    pub fn new(thread_creator: InputFilterThreadCreator) -> InputFilterThread {
+        Self {
+            thread_creator,
+            thread_callback_handler: ThreadCallbackHandler::new(),
+            inner: Arc::new(RwLock::new(InputFilterThreadInner {
+                next_timeout: i64::MAX,
+                is_finishing: false,
+            })),
+            looper: Arc::new(RwLock::new(Looper { cpp_thread: None })),
+        }
+    }
+
+    /// Listener requesting a timeout in future will receive a callback at or before the requested
+    /// time on the input filter thread.
+    /// {@see ThreadCallback.notify_timeout_expired(...)}
+    pub fn request_timeout_at_time(&self, when_nanos: i64) {
+        let mut need_wake = false;
+        {
+            // acquire filter lock
+            let filter_thread = &mut self.filter_thread();
+            if when_nanos < filter_thread.next_timeout {
+                filter_thread.next_timeout = when_nanos;
+                need_wake = true;
+            }
+        } // release filter lock
+        if need_wake {
+            self.wake();
+        }
+    }
+
+    /// Registers a callback listener.
+    ///
+    /// NOTE: If a listener with the same name already exists when registering using
+    /// {@see InputFilterThread.register_thread_callback(...)}, we will ignore the listener. You
+    /// must clear any previously registered listeners using
+    /// {@see InputFilterThread.unregister_thread_callback(...) before registering the new listener.
+    ///
+    /// NOTE: Also, registering a callback will start the looper if not already started.
+    pub fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+        self.thread_callback_handler.register_thread_callback(callback);
+        self.start();
+    }
+
+    /// Unregisters a callback listener.
+    ///
+    /// NOTE: Unregistering a callback will stop the looper if not other callback registered.
+    pub fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+        self.thread_callback_handler.unregister_thread_callback(callback);
+        // Stop the thread if no registered callbacks exist. We will recreate the thread when new
+        // callbacks are registered.
+        let has_callbacks = self.thread_callback_handler.has_callbacks();
+        if !has_callbacks {
+            self.stop();
+        }
+    }
+
+    fn start(&self) {
+        debug!("InputFilterThread: start thread");
+        {
+            // acquire looper lock
+            let looper = &mut self.looper();
+            if looper.cpp_thread.is_none() {
+                looper.cpp_thread = Some(self.thread_creator.create(
+                    &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
+                ));
+            }
+        } // release looper lock
+        self.set_finishing(false);
+    }
+
+    fn stop(&self) {
+        debug!("InputFilterThread: stop thread");
+        self.set_finishing(true);
+        self.wake();
+        {
+            // acquire looper lock
+            let looper = &mut self.looper();
+            if let Some(cpp_thread) = &looper.cpp_thread {
+                let _ = cpp_thread.finish();
+            }
+            // Clear all references
+            looper.cpp_thread = None;
+        } // release looper lock
+    }
+
+    fn set_finishing(&self, is_finishing: bool) {
+        let filter_thread = &mut self.filter_thread();
+        filter_thread.is_finishing = is_finishing;
+    }
+
+    fn loop_once(&self, now: i64) {
+        let mut wake_up_time = i64::MAX;
+        let mut timeout_expired = false;
+        {
+            // acquire thread lock
+            let filter_thread = &mut self.filter_thread();
+            if filter_thread.is_finishing {
+                // Thread is finishing so don't block processing on it and let it loop.
+                return;
+            }
+            if filter_thread.next_timeout != i64::MAX {
+                if filter_thread.next_timeout <= now {
+                    timeout_expired = true;
+                    filter_thread.next_timeout = i64::MAX;
+                } else {
+                    wake_up_time = filter_thread.next_timeout;
+                }
+            }
+        } // release thread lock
+        if timeout_expired {
+            self.thread_callback_handler.notify_timeout_expired(now);
+        }
+        self.sleep_until(wake_up_time);
+    }
+
+    fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> {
+        self.inner.write().unwrap()
+    }
+
+    fn sleep_until(&self, when_nanos: i64) {
+        let looper = self.looper.read().unwrap();
+        if let Some(cpp_thread) = &looper.cpp_thread {
+            let _ = cpp_thread.sleepUntil(when_nanos);
+        }
+    }
+
+    fn wake(&self) {
+        let looper = self.looper.read().unwrap();
+        if let Some(cpp_thread) = &looper.cpp_thread {
+            let _ = cpp_thread.wake();
+        }
+    }
+
+    fn looper(&self) -> RwLockWriteGuard<'_, Looper> {
+        self.looper.write().unwrap()
+    }
+}
+
+impl Interface for InputFilterThread {}
+
+impl IInputThreadCallback for InputFilterThread {
+    fn loopOnce(&self) -> binder::Result<()> {
+        self.loop_once(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds());
+        Result::Ok(())
+    }
+}
+
+#[derive(Default, Clone)]
+struct ThreadCallbackHandler(Arc<RwLock<ThreadCallbackHandlerInner>>);
+
+#[derive(Default)]
+struct ThreadCallbackHandlerInner {
+    callbacks: Vec<Box<dyn ThreadCallback + Send + Sync>>,
+}
+
+impl ThreadCallbackHandler {
+    fn new() -> Self {
+        Default::default()
+    }
+
+    fn has_callbacks(&self) -> bool {
+        !&self.0.read().unwrap().callbacks.is_empty()
+    }
+
+    fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+        let callbacks = &mut self.0.write().unwrap().callbacks;
+        if callbacks.iter().any(|x| x.name() == callback.name()) {
+            error!(
+                "InputFilterThread: register_thread_callback, callback {:?} already exists!",
+                callback.name()
+            );
+            return;
+        }
+        debug!(
+            "InputFilterThread: register_thread_callback, callback {:?} added!",
+            callback.name()
+        );
+        callbacks.push(callback);
+    }
+
+    fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+        let callbacks = &mut self.0.write().unwrap().callbacks;
+        if let Some(index) = callbacks.iter().position(|x| x.name() == callback.name()) {
+            callbacks.remove(index);
+            debug!(
+                "InputFilterThread: unregister_thread_callback, callback {:?} removed!",
+                callback.name()
+            );
+            return;
+        }
+        error!(
+            "InputFilterThread: unregister_thread_callback, callback {:?} doesn't exist",
+            callback.name()
+        );
+    }
+
+    fn notify_timeout_expired(&self, when_nanos: i64) {
+        let callbacks = &self.0.read().unwrap().callbacks;
+        for callback in callbacks.iter() {
+            callback.notify_timeout_expired(when_nanos);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
+    use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
+    use binder::Strong;
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{Arc, RwLock};
+    use std::time::Duration;
+
+    #[test]
+    fn test_register_callback_creates_cpp_thread() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let test_thread_callback = TestThreadCallback::new();
+        test_thread.register_thread_callback(Box::new(test_thread_callback));
+        assert!(test_callbacks.is_thread_running());
+    }
+
+    #[test]
+    fn test_unregister_callback_finishes_cpp_thread() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let test_thread_callback = TestThreadCallback::new();
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
+        test_thread.unregister_thread_callback(Box::new(test_thread_callback));
+        assert!(!test_callbacks.is_thread_running());
+    }
+
+    #[test]
+    fn test_notify_timeout_called_after_timeout_expired() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let test_thread_callback = TestThreadCallback::new();
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
+
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+        test_thread.request_timeout_at_time((now + 10) * 1000000);
+
+        std::thread::sleep(Duration::from_millis(100));
+        assert!(test_thread_callback.is_notify_timeout_called());
+    }
+
+    #[test]
+    fn test_notify_timeout_not_called_before_timeout_expired() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let test_thread_callback = TestThreadCallback::new();
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
+
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+        test_thread.request_timeout_at_time((now + 100) * 1000000);
+
+        std::thread::sleep(Duration::from_millis(10));
+        assert!(!test_thread_callback.is_notify_timeout_called());
+    }
+
+    fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+        InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+            Box::new(callbacks),
+        )))))
+    }
+}
+
+#[cfg(test)]
+pub mod test_thread_callback {
+    use crate::input_filter_thread::ThreadCallback;
+    use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+    #[derive(Default)]
+    struct TestThreadCallbackInner {
+        is_notify_timeout_called: bool,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestThreadCallback(Arc<RwLock<TestThreadCallbackInner>>);
+
+    impl TestThreadCallback {
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, TestThreadCallbackInner> {
+            self.0.write().unwrap()
+        }
+
+        pub fn is_notify_timeout_called(&self) -> bool {
+            self.0.read().unwrap().is_notify_timeout_called
+        }
+    }
+
+    impl ThreadCallback for TestThreadCallback {
+        fn notify_timeout_expired(&self, _when_nanos: i64) {
+            self.inner().is_notify_timeout_called = true;
+        }
+        fn name(&self) -> &str {
+            "TestThreadCallback"
+        }
+    }
+}
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
index 5abef68..4af7b84 100644
--- a/services/inputflinger/rust/lib.rs
+++ b/services/inputflinger/rust/lib.rs
@@ -21,6 +21,9 @@
 
 mod bounce_keys_filter;
 mod input_filter;
+mod input_filter_thread;
+mod slow_keys_filter;
+mod sticky_keys_filter;
 
 use crate::input_filter::InputFilter;
 use binder::{
@@ -62,9 +65,9 @@
 ///
 /// # Safety
 ///
-/// This function is safe iff `callback` is a valid pointer to an `AIBinder` interface of type
-/// `IInputFlingerRustBootstrapCallback`. The pointer must have had its reference count manually
-/// incremented using `AIBinder_incStrong`. See `binder::unstable_api::new_spibinder`.
+/// The provided `callback` must be a valid pointer to an `AIBinder` interface of type
+/// `IInputFlingerRustBootstrapCallback`, and the caller must give this function ownership of one
+/// strong refcount to the interface. See `binder::unstable_api::new_spibinder`.
 unsafe fn create_inputflinger_rust(callback: *mut ffi::IInputFlingerRustBootstrapCallbackAIBinder) {
     logger::init(
         logger::Config::default()
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
new file mode 100644
index 0000000..0f18a2f
--- /dev/null
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Slow keys input filter implementation.
+//! Slow keys is an accessibility feature to aid users who have physical disabilities, that allows
+//! the user to specify the duration for which one must press-and-hold a key before the system
+//! accepts the keypress.
+use crate::input_filter::Filter;
+use crate::input_filter_thread::{InputFilterThread, ThreadCallback};
+use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use log::debug;
+use std::collections::HashSet;
+use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
+
+// Policy flags from Input.h
+const POLICY_FLAG_DISABLE_KEY_REPEAT: i32 = 0x08000000;
+
+#[derive(Debug)]
+struct OngoingKeyDown {
+    scancode: i32,
+    device_id: i32,
+    down_time: i64,
+}
+
+struct SlowKeysFilterInner {
+    next: Box<dyn Filter + Send + Sync>,
+    slow_key_threshold_ns: i64,
+    external_devices: HashSet<i32>,
+    // This tracks KeyEvents that are blocked by Slow keys filter and will be passed through if the
+    // press duration exceeds the slow keys threshold.
+    pending_down_events: Vec<KeyEvent>,
+    // This tracks KeyEvent streams that have press duration greater than the slow keys threshold,
+    // hence any future ACTION_DOWN (if repeats are handled on HW side) or ACTION_UP are allowed to
+    // pass through without waiting.
+    ongoing_down_events: Vec<OngoingKeyDown>,
+    input_filter_thread: InputFilterThread,
+}
+
+#[derive(Clone)]
+pub struct SlowKeysFilter(Arc<RwLock<SlowKeysFilterInner>>);
+
+impl SlowKeysFilter {
+    /// Create a new SlowKeysFilter instance.
+    pub fn new(
+        next: Box<dyn Filter + Send + Sync>,
+        slow_key_threshold_ns: i64,
+        input_filter_thread: InputFilterThread,
+    ) -> SlowKeysFilter {
+        let filter = Self(Arc::new(RwLock::new(SlowKeysFilterInner {
+            next,
+            slow_key_threshold_ns,
+            external_devices: HashSet::new(),
+            pending_down_events: Vec::new(),
+            ongoing_down_events: Vec::new(),
+            input_filter_thread: input_filter_thread.clone(),
+        })));
+        input_filter_thread.register_thread_callback(Box::new(filter.clone()));
+        filter
+    }
+
+    fn read_inner(&self) -> RwLockReadGuard<'_, SlowKeysFilterInner> {
+        self.0.read().unwrap()
+    }
+
+    fn write_inner(&self) -> RwLockWriteGuard<'_, SlowKeysFilterInner> {
+        self.0.write().unwrap()
+    }
+
+    fn request_next_callback(&self) {
+        let slow_filter = &self.read_inner();
+        if slow_filter.pending_down_events.is_empty() {
+            return;
+        }
+        if let Some(event) = slow_filter.pending_down_events.iter().min_by_key(|x| x.downTime) {
+            slow_filter.input_filter_thread.request_timeout_at_time(event.downTime);
+        }
+    }
+}
+
+impl Filter for SlowKeysFilter {
+    fn notify_key(&mut self, event: &KeyEvent) {
+        {
+            // acquire write lock
+            let mut slow_filter = self.write_inner();
+            if !(slow_filter.external_devices.contains(&event.deviceId)
+                && event.source == Source::KEYBOARD)
+            {
+                slow_filter.next.notify_key(event);
+                return;
+            }
+            // Pass all events through if key down has already been processed
+            // Do update the downtime before sending the events through
+            if let Some(index) = slow_filter
+                .ongoing_down_events
+                .iter()
+                .position(|x| x.device_id == event.deviceId && x.scancode == event.scanCode)
+            {
+                let mut new_event = *event;
+                new_event.downTime = slow_filter.ongoing_down_events[index].down_time;
+                slow_filter.next.notify_key(&new_event);
+                if event.action == KeyEventAction::UP {
+                    slow_filter.ongoing_down_events.remove(index);
+                }
+                return;
+            }
+            match event.action {
+                KeyEventAction::DOWN => {
+                    if slow_filter
+                        .pending_down_events
+                        .iter()
+                        .any(|x| x.deviceId == event.deviceId && x.scanCode == event.scanCode)
+                    {
+                        debug!("Dropping key down event since another pending down event exists");
+                        return;
+                    }
+                    let mut pending_event = *event;
+                    pending_event.downTime += slow_filter.slow_key_threshold_ns;
+                    pending_event.eventTime = pending_event.downTime;
+                    // Currently a slow keys user ends up repeating the presses key quite often
+                    // since default repeat thresholds are very low, so blocking repeat for events
+                    // when slow keys is enabled.
+                    // TODO(b/322327461): Allow key repeat with slow keys, once repeat key rate and
+                    //  thresholds can be modified in the settings.
+                    pending_event.policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
+                    slow_filter.pending_down_events.push(pending_event);
+                }
+                KeyEventAction::UP => {
+                    debug!("Dropping key up event due to insufficient press duration");
+                    if let Some(index) = slow_filter
+                        .pending_down_events
+                        .iter()
+                        .position(|x| x.deviceId == event.deviceId && x.scanCode == event.scanCode)
+                    {
+                        slow_filter.pending_down_events.remove(index);
+                    }
+                }
+                _ => (),
+            }
+        } // release write lock
+        self.request_next_callback();
+    }
+
+    fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+        let mut slow_filter = self.write_inner();
+        slow_filter
+            .pending_down_events
+            .retain(|event| device_infos.iter().any(|x| event.deviceId == x.deviceId));
+        slow_filter
+            .ongoing_down_events
+            .retain(|event| device_infos.iter().any(|x| event.device_id == x.deviceId));
+        slow_filter.external_devices.clear();
+        for device_info in device_infos {
+            if device_info.external {
+                slow_filter.external_devices.insert(device_info.deviceId);
+            }
+        }
+        slow_filter.next.notify_devices_changed(device_infos);
+    }
+
+    fn destroy(&mut self) {
+        let mut slow_filter = self.write_inner();
+        slow_filter.input_filter_thread.unregister_thread_callback(Box::new(self.clone()));
+        slow_filter.next.destroy();
+    }
+}
+
+impl ThreadCallback for SlowKeysFilter {
+    fn notify_timeout_expired(&self, when_nanos: i64) {
+        {
+            // acquire write lock
+            let slow_filter = &mut self.write_inner();
+            for event in slow_filter.pending_down_events.clone() {
+                if event.downTime <= when_nanos {
+                    slow_filter.next.notify_key(&event);
+                    slow_filter.ongoing_down_events.push(OngoingKeyDown {
+                        scancode: event.scanCode,
+                        device_id: event.deviceId,
+                        down_time: event.downTime,
+                    });
+                }
+            }
+            slow_filter.pending_down_events.retain(|event| event.downTime > when_nanos);
+        } // release write lock
+        self.request_next_callback();
+    }
+
+    fn name(&self) -> &str {
+        "slow_keys_filter"
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, InputFilterThreadCreator,
+    };
+    use crate::input_filter_thread::InputFilterThread;
+    use crate::slow_keys_filter::{SlowKeysFilter, POLICY_FLAG_DISABLE_KEY_REPEAT};
+    use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use binder::Strong;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+    };
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{Arc, RwLock};
+    use std::time::Duration;
+
+    static BASE_KEY_EVENT: KeyEvent = KeyEvent {
+        id: 1,
+        deviceId: 1,
+        downTime: 0,
+        readTime: 0,
+        eventTime: 0,
+        source: Source::KEYBOARD,
+        displayId: 0,
+        policyFlags: 0,
+        action: KeyEventAction::DOWN,
+        flags: 0,
+        keyCode: 1,
+        scanCode: 0,
+        metaState: 0,
+    };
+
+    static SLOW_KEYS_THRESHOLD_NS: i64 = 100 * 1000000; // 100 ms
+
+    #[test]
+    fn test_is_notify_key_for_internal_keyboard_not_blocked() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_internal_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+        );
+
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_for_external_stylus_not_blocked() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+        );
+
+        let event =
+            KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+        );
+        let down_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: down_time,
+            eventTime: down_time,
+            ..BASE_KEY_EVENT
+        });
+        assert!(next.last_event().is_none());
+
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::DOWN,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                policyFlags: POLICY_FLAG_DISABLE_KEY_REPEAT,
+                ..BASE_KEY_EVENT
+            }
+        );
+
+        let up_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: down_time,
+            eventTime: up_time,
+            ..BASE_KEY_EVENT
+        });
+
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::UP,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: up_time,
+                ..BASE_KEY_EVENT
+            }
+        );
+    }
+
+    #[test]
+    fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+        );
+        let mut now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
+
+        std::thread::sleep(Duration::from_nanos(SLOW_KEYS_THRESHOLD_NS as u64 / 2));
+
+        now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
+
+        assert!(next.last_event().is_none());
+    }
+
+    #[test]
+    fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+        );
+
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
+
+        filter.notify_devices_changed(&[]);
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
+
+        assert!(next.last_event().is_none());
+    }
+
+    fn setup_filter_with_external_device(
+        next: Box<dyn Filter + Send + Sync>,
+        test_thread: InputFilterThread,
+        device_id: i32,
+        threshold: i64,
+    ) -> SlowKeysFilter {
+        setup_filter_with_devices(
+            next,
+            test_thread,
+            &[DeviceInfo { deviceId: device_id, external: true }],
+            threshold,
+        )
+    }
+
+    fn setup_filter_with_internal_device(
+        next: Box<dyn Filter + Send + Sync>,
+        test_thread: InputFilterThread,
+        device_id: i32,
+        threshold: i64,
+    ) -> SlowKeysFilter {
+        setup_filter_with_devices(
+            next,
+            test_thread,
+            &[DeviceInfo { deviceId: device_id, external: false }],
+            threshold,
+        )
+    }
+
+    fn setup_filter_with_devices(
+        next: Box<dyn Filter + Send + Sync>,
+        test_thread: InputFilterThread,
+        devices: &[DeviceInfo],
+        threshold: i64,
+    ) -> SlowKeysFilter {
+        let mut filter = SlowKeysFilter::new(next, threshold, test_thread);
+        filter.notify_devices_changed(devices);
+        filter
+    }
+
+    fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+        InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+            Box::new(callbacks),
+        )))))
+    }
+}
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
new file mode 100644
index 0000000..6c2277c
--- /dev/null
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Sticky keys input filter implementation.
+//! Sticky keys is an accessibility feature that assists users who have physical disabilities or
+//! helps users reduce repetitive strain injury. It serializes keystrokes instead of pressing
+//! multiple keys at a time, allowing the user to press and release a modifier key, such as Shift,
+//! Ctrl, Alt, or any other modifier key, and have it remain active until any other key is pressed.
+use crate::input_filter::{Filter, ModifierStateListener};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use std::collections::HashSet;
+
+// Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h
+const KEYCODE_ALT_LEFT: i32 = 57;
+const KEYCODE_ALT_RIGHT: i32 = 58;
+const KEYCODE_SHIFT_LEFT: i32 = 59;
+const KEYCODE_SHIFT_RIGHT: i32 = 60;
+const KEYCODE_SYM: i32 = 63;
+const KEYCODE_CTRL_LEFT: i32 = 113;
+const KEYCODE_CTRL_RIGHT: i32 = 114;
+const KEYCODE_CAPS_LOCK: i32 = 115;
+const KEYCODE_SCROLL_LOCK: i32 = 116;
+const KEYCODE_META_LEFT: i32 = 117;
+const KEYCODE_META_RIGHT: i32 = 118;
+const KEYCODE_FUNCTION: i32 = 119;
+const KEYCODE_NUM_LOCK: i32 = 143;
+
+// Modifier states: values are from /frameworks/native/include/android/input.h
+const META_ALT_ON: u32 = 0x02;
+const META_ALT_LEFT_ON: u32 = 0x10;
+const META_ALT_RIGHT_ON: u32 = 0x20;
+const META_SHIFT_ON: u32 = 0x01;
+const META_SHIFT_LEFT_ON: u32 = 0x40;
+const META_SHIFT_RIGHT_ON: u32 = 0x80;
+const META_CTRL_ON: u32 = 0x1000;
+const META_CTRL_LEFT_ON: u32 = 0x2000;
+const META_CTRL_RIGHT_ON: u32 = 0x4000;
+const META_META_ON: u32 = 0x10000;
+const META_META_LEFT_ON: u32 = 0x20000;
+const META_META_RIGHT_ON: u32 = 0x40000;
+
+pub struct StickyKeysFilter {
+    next: Box<dyn Filter + Send + Sync>,
+    listener: ModifierStateListener,
+    /// Tracking devices that contributed to the modifier state.
+    contributing_devices: HashSet<i32>,
+    /// State describing the current enabled modifiers. This contain both locked and non-locked
+    /// modifier state bits.
+    modifier_state: u32,
+    /// State describing the current locked modifiers. These modifiers will not be cleared on a
+    /// non-modifier key press. They will be cleared only if the locked modifier key is pressed
+    /// again.
+    locked_modifier_state: u32,
+}
+
+impl StickyKeysFilter {
+    /// Create a new StickyKeysFilter instance.
+    pub fn new(
+        next: Box<dyn Filter + Send + Sync>,
+        listener: ModifierStateListener,
+    ) -> StickyKeysFilter {
+        Self {
+            next,
+            listener,
+            contributing_devices: HashSet::new(),
+            modifier_state: 0,
+            locked_modifier_state: 0,
+        }
+    }
+}
+
+impl Filter for StickyKeysFilter {
+    fn notify_key(&mut self, event: &KeyEvent) {
+        let up = event.action == KeyEventAction::UP;
+        let mut modifier_state = self.modifier_state;
+        let mut locked_modifier_state = self.locked_modifier_state;
+        if !is_ephemeral_modifier_key(event.keyCode) {
+            // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like
+            // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with
+            // the KeyEvent.
+            let old_modifier_state = event.metaState as u32;
+            let mut new_event = *event;
+            // Send the current modifier state with the key event before clearing non-locked
+            // modifier state
+            new_event.metaState =
+                (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state) as i32;
+            self.next.notify_key(&new_event);
+            if up && !is_modifier_key(event.keyCode) {
+                modifier_state =
+                    clear_ephemeral_modifier_state(modifier_state) | locked_modifier_state;
+            }
+        } else if up {
+            // Update contributing devices to track keyboards
+            self.contributing_devices.insert(event.deviceId);
+            // If ephemeral modifier key, capture the key and update the sticky modifier states
+            let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode);
+            let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode);
+            if locked_modifier_state & modifier_key_mask != 0 {
+                locked_modifier_state &= !symmetrical_modifier_key_mask;
+                modifier_state &= !symmetrical_modifier_key_mask;
+            } else if modifier_key_mask & modifier_state != 0 {
+                locked_modifier_state |= modifier_key_mask;
+                modifier_state =
+                    (modifier_state & !symmetrical_modifier_key_mask) | modifier_key_mask;
+            } else {
+                modifier_state |= modifier_key_mask;
+            }
+        }
+        if self.modifier_state != modifier_state
+            || self.locked_modifier_state != locked_modifier_state
+        {
+            self.modifier_state = modifier_state;
+            self.locked_modifier_state = locked_modifier_state;
+            self.listener.modifier_state_changed(modifier_state, locked_modifier_state);
+        }
+    }
+
+    fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+        // Clear state if all contributing devices removed
+        self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId));
+        if self.contributing_devices.is_empty()
+            && (self.modifier_state != 0 || self.locked_modifier_state != 0)
+        {
+            self.modifier_state = 0;
+            self.locked_modifier_state = 0;
+            self.listener.modifier_state_changed(0, 0);
+        }
+        self.next.notify_devices_changed(device_infos);
+    }
+
+    fn destroy(&mut self) {
+        self.next.destroy();
+    }
+}
+
+fn is_modifier_key(keycode: i32) -> bool {
+    matches!(
+        keycode,
+        KEYCODE_ALT_LEFT
+            | KEYCODE_ALT_RIGHT
+            | KEYCODE_SHIFT_LEFT
+            | KEYCODE_SHIFT_RIGHT
+            | KEYCODE_CTRL_LEFT
+            | KEYCODE_CTRL_RIGHT
+            | KEYCODE_META_LEFT
+            | KEYCODE_META_RIGHT
+            | KEYCODE_SYM
+            | KEYCODE_FUNCTION
+            | KEYCODE_CAPS_LOCK
+            | KEYCODE_NUM_LOCK
+            | KEYCODE_SCROLL_LOCK
+    )
+}
+
+fn is_ephemeral_modifier_key(keycode: i32) -> bool {
+    matches!(
+        keycode,
+        KEYCODE_ALT_LEFT
+            | KEYCODE_ALT_RIGHT
+            | KEYCODE_SHIFT_LEFT
+            | KEYCODE_SHIFT_RIGHT
+            | KEYCODE_CTRL_LEFT
+            | KEYCODE_CTRL_RIGHT
+            | KEYCODE_META_LEFT
+            | KEYCODE_META_RIGHT
+    )
+}
+
+fn get_ephemeral_modifier_key_mask(keycode: i32) -> u32 {
+    match keycode {
+        KEYCODE_ALT_LEFT => META_ALT_LEFT_ON | META_ALT_ON,
+        KEYCODE_ALT_RIGHT => META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_SHIFT_LEFT => META_SHIFT_LEFT_ON | META_SHIFT_ON,
+        KEYCODE_SHIFT_RIGHT => META_SHIFT_RIGHT_ON | META_SHIFT_ON,
+        KEYCODE_CTRL_LEFT => META_CTRL_LEFT_ON | META_CTRL_ON,
+        KEYCODE_CTRL_RIGHT => META_CTRL_RIGHT_ON | META_CTRL_ON,
+        KEYCODE_META_LEFT => META_META_LEFT_ON | META_META_ON,
+        KEYCODE_META_RIGHT => META_META_RIGHT_ON | META_META_ON,
+        _ => 0,
+    }
+}
+
+/// Modifier mask including both left and right versions of a modifier key.
+fn get_symmetrical_modifier_key_mask(keycode: i32) -> u32 {
+    match keycode {
+        KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => META_ALT_LEFT_ON | META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_SHIFT_LEFT | KEYCODE_SHIFT_RIGHT => {
+            META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON | META_SHIFT_ON
+        }
+        KEYCODE_CTRL_LEFT | KEYCODE_CTRL_RIGHT => {
+            META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON | META_CTRL_ON
+        }
+        KEYCODE_META_LEFT | KEYCODE_META_RIGHT => {
+            META_META_LEFT_ON | META_META_RIGHT_ON | META_META_ON
+        }
+        _ => 0,
+    }
+}
+
+fn clear_ephemeral_modifier_state(modifier_state: u32) -> u32 {
+    modifier_state
+        & !(META_ALT_LEFT_ON
+            | META_ALT_RIGHT_ON
+            | META_ALT_ON
+            | META_SHIFT_LEFT_ON
+            | META_SHIFT_RIGHT_ON
+            | META_SHIFT_ON
+            | META_CTRL_LEFT_ON
+            | META_CTRL_RIGHT_ON
+            | META_CTRL_ON
+            | META_META_LEFT_ON
+            | META_META_RIGHT_ON
+            | META_META_ON)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, ModifierStateListener,
+    };
+    use crate::sticky_keys_filter::{
+        StickyKeysFilter, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CAPS_LOCK,
+        KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_FUNCTION, KEYCODE_META_LEFT,
+        KEYCODE_META_RIGHT, KEYCODE_NUM_LOCK, KEYCODE_SCROLL_LOCK, KEYCODE_SHIFT_LEFT,
+        KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, META_ALT_LEFT_ON, META_ALT_ON, META_ALT_RIGHT_ON,
+        META_CTRL_LEFT_ON, META_CTRL_ON, META_CTRL_RIGHT_ON, META_META_LEFT_ON, META_META_ON,
+        META_META_RIGHT_ON, META_SHIFT_LEFT_ON, META_SHIFT_ON, META_SHIFT_RIGHT_ON,
+    };
+    use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use binder::Strong;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+        KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+    };
+    use std::sync::{Arc, RwLock};
+
+    static DEVICE_ID: i32 = 1;
+    static KEY_A: i32 = 29;
+    static BASE_KEY_DOWN: KeyEvent = KeyEvent {
+        id: 1,
+        deviceId: DEVICE_ID,
+        downTime: 0,
+        readTime: 0,
+        eventTime: 0,
+        source: Source::KEYBOARD,
+        displayId: 0,
+        policyFlags: 0,
+        action: KeyEventAction::DOWN,
+        flags: 0,
+        keyCode: 0,
+        scanCode: 0,
+        metaState: 0,
+    };
+
+    static BASE_KEY_UP: KeyEvent = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_DOWN };
+
+    #[test]
+    fn test_notify_key_consumes_ephemeral_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let key_codes = &[
+            KEYCODE_ALT_LEFT,
+            KEYCODE_ALT_RIGHT,
+            KEYCODE_CTRL_LEFT,
+            KEYCODE_CTRL_RIGHT,
+            KEYCODE_SHIFT_LEFT,
+            KEYCODE_SHIFT_RIGHT,
+            KEYCODE_META_LEFT,
+            KEYCODE_META_RIGHT,
+        ];
+        for key_code in key_codes.iter() {
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN });
+            assert!(test_filter.last_event().is_none());
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_UP });
+            assert!(test_filter.last_event().is_none());
+        }
+    }
+
+    #[test]
+    fn test_notify_key_passes_non_ephemeral_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let key_codes = &[
+            KEYCODE_CAPS_LOCK,
+            KEYCODE_NUM_LOCK,
+            KEYCODE_SCROLL_LOCK,
+            KEYCODE_FUNCTION,
+            KEYCODE_SYM,
+        ];
+        for key_code in key_codes.iter() {
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_UP };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+        }
+    }
+
+    #[test]
+    fn test_notify_key_passes_non_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN };
+        sticky_keys_filter.notify_key(&event);
+        assert_eq!(test_filter.last_event().unwrap(), event);
+
+        let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP };
+        sticky_keys_filter.notify_key(&event);
+        assert_eq!(test_filter.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_modifier_state_updated_on_modifier_key_press() {
+        let mut test_filter = TestFilter::new();
+        let mut test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let test_states = &[
+            (KEYCODE_ALT_LEFT, META_ALT_ON | META_ALT_LEFT_ON),
+            (KEYCODE_ALT_RIGHT, META_ALT_ON | META_ALT_RIGHT_ON),
+            (KEYCODE_CTRL_LEFT, META_CTRL_ON | META_CTRL_LEFT_ON),
+            (KEYCODE_CTRL_RIGHT, META_CTRL_ON | META_CTRL_RIGHT_ON),
+            (KEYCODE_SHIFT_LEFT, META_SHIFT_ON | META_SHIFT_LEFT_ON),
+            (KEYCODE_SHIFT_RIGHT, META_SHIFT_ON | META_SHIFT_RIGHT_ON),
+            (KEYCODE_META_LEFT, META_META_ON | META_META_LEFT_ON),
+            (KEYCODE_META_RIGHT, META_META_ON | META_META_RIGHT_ON),
+        ];
+        for test_state in test_states.iter() {
+            test_filter.clear();
+            test_callbacks.clear();
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            // Re-send keys to lock it
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
+
+            // Re-send keys to clear
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        }
+    }
+
+    #[test]
+    fn test_modifier_state_cleared_on_non_modifier_key_press() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+    }
+
+    #[test]
+    fn test_locked_modifier_state_not_cleared_on_non_modifier_key_press() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_UP });
+
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            META_SHIFT_LEFT_ON | META_SHIFT_ON | META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+    }
+
+    #[test]
+    fn test_key_events_have_sticky_modifier_state() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        assert_eq!(
+            test_filter.last_event().unwrap().metaState as u32,
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+        assert_eq!(
+            test_filter.last_event().unwrap().metaState as u32,
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+    }
+
+    #[test]
+    fn test_modifier_state_not_cleared_until_all_devices_removed() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 1,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_DOWN
+        });
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 1,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_UP
+        });
+
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 2,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_DOWN
+        });
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 2,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_UP
+        });
+
+        sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]);
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_devices_changed(&[]);
+        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+    }
+
+    fn setup_filter(
+        next: Box<dyn Filter + Send + Sync>,
+        callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+    ) -> StickyKeysFilter {
+        StickyKeysFilter::new(next, ModifierStateListener::new(callbacks))
+    }
+}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 9c9f643..9b5db23 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -22,6 +22,15 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+// Source files shared with InputDispatcher's benchmarks and fuzzers
+filegroup {
+    name: "inputdispatcher_common_test_sources",
+    srcs: [
+        "FakeInputDispatcherPolicy.cpp",
+        "FakeWindows.cpp",
+    ],
+}
+
 cc_test {
     name: "inputflinger_tests",
     host_supported: true,
@@ -38,12 +47,14 @@
         "libinputflinger_defaults",
     ],
     srcs: [
+        ":inputdispatcher_common_test_sources",
         "AnrTracker_test.cpp",
         "CapturedTouchpadEventConverter_test.cpp",
         "CursorInputMapper_test.cpp",
         "EventHub_test.cpp",
         "FakeEventHub.cpp",
         "FakeInputReaderPolicy.cpp",
+        "FakeInputTracingBackend.cpp",
         "FakePointerController.cpp",
         "FocusResolver_test.cpp",
         "GestureConverter_test.cpp",
@@ -56,6 +67,8 @@
         "InputProcessorConverter_test.cpp",
         "InputDispatcher_test.cpp",
         "InputReader_test.cpp",
+        "InputTraceSession.cpp",
+        "InputTracingTest.cpp",
         "InstrumentedInputReader.cpp",
         "LatencyTracker_test.cpp",
         "MultiTouchMotionAccumulator_test.cpp",
@@ -68,6 +81,7 @@
         "TimerProvider_test.cpp",
         "TestInputListener.cpp",
         "TouchpadInputMapper_test.cpp",
+        "MultiTouchInputMapper_test.cpp",
         "KeyboardInputMapper_test.cpp",
         "UinputDevice.cpp",
         "UnwantedInteractionBlocker_test.cpp",
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 6d6b7d8..83074ff 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -16,14 +16,25 @@
 
 #include "CursorInputMapper.h"
 
+#include <list>
+#include <string>
+#include <tuple>
+#include <variant>
+
 #include <android-base/logging.h>
 #include <com_android_input_flags.h>
 #include <gtest/gtest.h>
+#include <input/DisplayViewport.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <utils/Timers.h>
 
-#include "FakePointerController.h"
 #include "InputMapperTest.h"
+#include "InputReaderBase.h"
 #include "InterfaceMocks.h"
+#include "NotifyArgs.h"
 #include "TestEventMatchers.h"
+#include "ui/Rotation.h"
 
 #define TAG "CursorInputMapper_test"
 
@@ -39,24 +50,98 @@
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
 constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
-constexpr int32_t DISPLAY_ID = 0;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
-constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+
+constexpr int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+namespace {
+
+DisplayViewport createPrimaryViewport(ui::Rotation orientation) {
+    const bool isRotated =
+            orientation == ui::Rotation::Rotation90 || orientation == ui::Rotation::Rotation270;
+    DisplayViewport v;
+    v.displayId = DISPLAY_ID;
+    v.orientation = orientation;
+    v.logicalRight = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.logicalBottom = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.physicalRight = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.physicalBottom = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.deviceWidth = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.deviceHeight = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.isActive = true;
+    v.uniqueId = "local:1";
+    return v;
+}
+
+DisplayViewport createSecondaryViewport() {
+    DisplayViewport v;
+    v.displayId = SECONDARY_DISPLAY_ID;
+    v.orientation = ui::Rotation::Rotation0;
+    v.logicalRight = DISPLAY_HEIGHT;
+    v.logicalBottom = DISPLAY_WIDTH;
+    v.physicalRight = DISPLAY_HEIGHT;
+    v.physicalBottom = DISPLAY_WIDTH;
+    v.deviceWidth = DISPLAY_HEIGHT;
+    v.deviceHeight = DISPLAY_WIDTH;
+    v.isActive = true;
+    v.uniqueId = "local:2";
+    v.type = ViewportType::EXTERNAL;
+    return v;
+}
+
+/**
+ * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper.
+ *
+ * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates
+ * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input
+ * device doesn't set its associated viewport when it's configured.
+ *
+ * TODO(b/319217713): work out a way to avoid this fake.
+ */
+class ViewportFakingInputDeviceContext : public InputDeviceContext {
+public:
+    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
+                                     std::optional<DisplayViewport> viewport)
+          : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {}
+
+    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
+                                     ui::Rotation orientation)
+          : ViewportFakingInputDeviceContext(device, eventHubId,
+                                             createPrimaryViewport(orientation)) {}
+
+    std::optional<DisplayViewport> getAssociatedViewport() const override {
+        return mAssociatedViewport;
+    }
+
+    void setViewport(const std::optional<DisplayViewport>& viewport) {
+        mAssociatedViewport = viewport;
+    }
+
+private:
+    std::optional<DisplayViewport> mAssociatedViewport;
+};
+
+} // namespace
 
 namespace input_flags = com::android::input::flags;
 
 /**
  * Unit tests for CursorInputMapper.
- * This class is named 'CursorInputMapperUnitTest' to avoid name collision with the existing
- * 'CursorInputMapperTest'. If all of the CursorInputMapper tests are migrated here, the name
- * can be simplified to 'CursorInputMapperTest'.
- * TODO(b/283812079): move CursorInputMapper tests here.
+ * These classes are named 'CursorInputMapperUnitTest...' to avoid name collision with the existing
+ * 'CursorInputMapperTest...' classes. If all of the CursorInputMapper tests are migrated here, the
+ * name can be simplified to 'CursorInputMapperTest'.
+ *
+ * TODO(b/283812079): move the remaining CursorInputMapper tests here. The ones that are left all
+ *   depend on viewport association, for which we'll need to fake InputDeviceContext.
  */
-class CursorInputMapperUnitTest : public InputMapperUnitTest {
+class CursorInputMapperUnitTestBase : public InputMapperUnitTest {
 protected:
-    void SetUp() override {
-        InputMapperUnitTest::SetUp();
+    void SetUp() override { SetUpWithBus(BUS_USB); }
+    void SetUpWithBus(int bus) override {
+        InputMapperUnitTest::SetUpWithBus(bus);
 
         // Current scan code state - all keys are UP by default
         setScanCodeState(KeyState::UP,
@@ -68,34 +153,71 @@
                 .WillRepeatedly(Return(false));
 
         mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-        mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                        /*isActive=*/true, "local:0", NO_PORT,
-                                        ViewportType::INTERNAL);
+        mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
+    }
 
+    void createMapper() {
+        createDevice();
         mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 
     void setPointerCapture(bool enabled) {
-        mReaderConfiguration.pointerCaptureRequest.enable = enabled;
+        mReaderConfiguration.pointerCaptureRequest.window = enabled ? sp<BBinder>::make() : nullptr;
         mReaderConfiguration.pointerCaptureRequest.seq = 1;
         int32_t generation = mDevice->getGeneration();
         std::list<NotifyArgs> args =
                 mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                      InputReaderConfiguration::Change::POINTER_CAPTURE);
         ASSERT_THAT(args,
-                    ElementsAre(
-                            VariantWith<NotifyDeviceResetArgs>(AllOf(WithDeviceId(DEVICE_ID)))));
+                    ElementsAre(VariantWith<NotifyDeviceResetArgs>(
+                            AllOf(WithDeviceId(DEVICE_ID), WithEventTime(ARBITRARY_TIME)))));
 
         // Check that generation also got bumped
         ASSERT_GT(mDevice->getGeneration(), generation);
     }
+
+    void testMotionRotation(int32_t originalX, int32_t originalY, int32_t rotatedX,
+                            int32_t rotatedY) {
+        std::list<NotifyArgs> args;
+        args += process(ARBITRARY_TIME, EV_REL, REL_X, originalX);
+        args += process(ARBITRARY_TIME, EV_REL, REL_Y, originalY);
+        args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        ASSERT_THAT(args,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(ACTION_MOVE),
+                                  WithCoords(float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
+                                             float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD)))));
+    }
 };
 
+class CursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_new_mouse_pointer_ballistics(false);
+        CursorInputMapperUnitTestBase::SetUp();
+    }
+};
+
+TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsMouseInPointerMode) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mMapper->getSources());
+}
+
+TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsTrackballInNavigationMode) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mMapper->getSources());
+}
+
 /**
  * Move the mouse and then click the button. Check whether HOVER_EXIT is generated when hovering
  * ends. Currently, it is not.
  */
 TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) {
+    createMapper();
     std::list<NotifyArgs> args;
 
     // Move the cursor a little
@@ -139,6 +261,7 @@
  * When it's not SOURCE_MOUSE, CursorInputMapper doesn't populate cursor position values.
  */
 TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) {
+    createMapper();
     setPointerCapture(true);
     std::list<NotifyArgs> args;
 
@@ -196,12 +319,9 @@
 
     // Disable pointer capture. Afterwards, events should be generated the usual way.
     setPointerCapture(false);
-    const auto expectedCoords = input_flags::enable_pointer_choreographer()
-            ? WithCoords(0, 0)
-            : WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
-    const auto expectedCursorPosition = input_flags::enable_pointer_choreographer()
-            ? WithCursorPosition(INVALID_CURSOR_POSITION, INVALID_CURSOR_POSITION)
-            : WithCursorPosition(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
+    const auto expectedCoords = WithCoords(0, 0);
+    const auto expectedCursorPosition =
+            WithCursorPosition(INVALID_CURSOR_POSITION, INVALID_CURSOR_POSITION);
     args.clear();
     args += process(EV_REL, REL_X, 10);
     args += process(EV_REL, REL_Y, 20);
@@ -213,4 +333,822 @@
                               WithRelativeMotion(10.0f, 20.0f)))));
 }
 
+TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsScaledRangeInNavigationMode) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
+                                              -1.0f, 1.0f, 0.0f,
+                                              1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL,
+                                              -1.0f, 1.0f, 0.0f,
+                                              1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_TRACKBALL, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldSetAllFieldsAndIncludeGlobalMetaState) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    EXPECT_CALL(mMockInputReaderContext, getGlobalMetaState())
+            .WillRepeatedly(Return(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON));
+
+    std::list<NotifyArgs> args;
+
+    // Button press.
+    // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPointerCount(1), WithPointerId(0, 0),
+                                          WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
+                                          WithPressure(1.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPointerCount(1), WithPointerId(0, 0),
+                                          WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
+                                          WithPressure(1.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME)))));
+    args.clear();
+
+    // Button release.  Should have same down time.
+    args += process(ARBITRARY_TIME + 1, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME + 1, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME + 1),
+                                          WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(0), WithPointerCount(1),
+                                          WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME + 1),
+                                          WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(0), WithPointerCount(1),
+                                          WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentXYUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Motion in X but not Y.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f),
+                              WithPressure(0.0f)))));
+    args.clear();
+
+    // Motion in Y but not X.
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, -2);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                              WithPressure(0.0f)))));
+    args.clear();
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentButtonUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Button press.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    // Button release.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleCombinedXYAndButtonUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Combined X, Y and Button.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, -2);
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                     -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                     -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithPressure(1.0f)))));
+    args.clear();
+
+    // Move X, Y a bit while pressed.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 2);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                         1.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                              WithPressure(1.0f)))));
+    args.clear();
+
+    // Release Button.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+    args.clear();
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldNotRotateMotionsWhenOrientationAware) {
+    // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not
+    // need to be rotated.
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    mPropertyMap.addProperty("cursor.orientationAware", "1");
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation90);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1,  1));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldRotateMotionsWhenNotOrientationAware) {
+    // Since InputReader works in the un-rotated coordinate space, only devices that are not
+    // orientation-aware are affected by display rotation.
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation0);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1,  1));
+
+    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation90));
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1, -1));
+
+    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation180));
+    args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1, -1));
+
+    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation270));
+    args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1,  1));
+}
+
+TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsRangeFromPolicy) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    mFakePolicy->clearViewports();
+    createMapper();
+
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    // Initially there should not be a valid motion range because there's no viewport or pointer
+    // bounds.
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+
+    // When the viewport and the default pointer display ID is set, then there should be a valid
+    // motion range.
+    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+    mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(systemTime(), mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    InputDeviceInfo info2;
+    mMapper->populateDeviceInfo(info2);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0,
+                                              DISPLAY_WIDTH - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0,
+                                              DISPLAY_HEIGHT - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdWithAssociatedViewport) {
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+    // Set up the secondary display as the display on which the pointer should be shown.
+    // The InputDevice is not associated with any display.
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+    // Ensure input events are generated for the secondary display.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(0.0f, 0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest,
+       ConfigureDisplayIdShouldGenerateEventForMismatchedPointerDisplay) {
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+    // Set up the primary display as the display on which the pointer should be shown.
+    createDevice();
+    // Associate the InputDevice with the secondary display.
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    // With PointerChoreographer enabled, there could be a PointerController for the associated
+    // display even if it is different from the pointer display. So the mapper should generate an
+    // event.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(0.0f, 0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleAllButtonsWithZeroCoords) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // press BTN_LEFT, release BTN_LEFT
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f)))));
+    args.clear();
+
+    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1);
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+}
+
+class CursorInputMapperButtonKeyTest
+      : public CursorInputMapperUnitTest,
+        public testing::WithParamInterface<
+                std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/,
+                           int32_t /*expectedKeyCode*/>> {};
+
+TEST_P(CursorInputMapperButtonKeyTest, ProcessShouldHandleButtonKeyWithZeroCoords) {
+    auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam();
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                                             WithKeyCode(expectedKeyCode))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                                             WithKeyCode(expectedKeyCode)))));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        SideExtraBackAndForward, CursorInputMapperButtonKeyTest,
+        testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD),
+                        std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD,
+                                        AKEYCODE_FORWARD)));
+
+TEST_F(CursorInputMapperUnitTest, ProcessWhenModeIsPointerShouldKeepZeroCoords) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(0.0f, 0.0f), WithPressure(0.0f), WithSize(0.0f),
+                              WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f),
+                              WithOrientation(0.0f), WithDistance(0.0f)))));
+}
+
+/**
+ * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
+ * pointer acceleration or speed processing should not be applied.
+ */
+TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesVelocityProcessing) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
+                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
+    mReaderConfiguration.pointerVelocityControlParameters = testParams;
+    mFakePolicy->setVelocityControlParams(testParams);
+    createMapper();
+
+    NotifyMotionArgs motionArgs;
+    std::list<NotifyArgs> args;
+
+    // Move and verify scale is applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))));
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_GT(relX, 10);
+    ASSERT_GT(relY, 20);
+    args.clear();
+
+    // Enable Pointer Capture
+    setPointerCapture(true);
+
+    // Move and verify scale is not applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))));
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_EQ(10, relX2);
+    ASSERT_EQ(20, relY2);
+}
+
+TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdNoAssociatedViewport) {
+    // Set up the default display.
+    mFakePolicy->clearViewports();
+    mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
+
+    // Set up the secondary display as the display on which the pointer should be shown.
+    // The InputDevice is not associated with any display.
+    mFakePolicy->addDisplayViewport(createSecondaryViewport());
+    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+
+    createMapper();
+
+    // Ensure input events are generated without display ID or coords, because they will be decided
+    // later by PointerChoreographer.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithSource(AINPUT_SOURCE_MOUSE),
+                              WithDisplayId(ui::LogicalDisplayId::INVALID),
+                              WithCoords(0.0f, 0.0f)))));
+}
+
+// TODO(b/320433834): De-duplicate the test cases once the flag is removed.
+class CursorInputMapperUnitTestWithNewBallistics : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_new_mouse_pointer_ballistics(true);
+        CursorInputMapperUnitTestBase::SetUp();
+    }
+};
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, PointerCaptureDisablesVelocityProcessing) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    NotifyMotionArgs motionArgs;
+    std::list<NotifyArgs> args;
+
+    // Move and verify scale is applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_GT(relX, 10);
+    ASSERT_GT(relY, 20);
+    args.clear();
+
+    // Enable Pointer Capture
+    setPointerCapture(true);
+
+    // Move and verify scale is not applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_EQ(10, relX2);
+    ASSERT_EQ(20, relY2);
+}
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationWithAssociatedViewport) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
+    mReaderConfiguration.setDisplayViewports({primaryViewport});
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, primaryViewport);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+
+    // Verify that acceleration is being applied by default by checking that the movement is scaled.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithDisplayId(DISPLAY_ID)))));
+    const auto& coords = get<NotifyMotionArgs>(args.back()).pointerCoords[0];
+    ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), 10.f);
+    ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), 20.f);
+
+    // Disable acceleration for the display, and verify that acceleration is no longer applied.
+    mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID);
+    args += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                 InputReaderConfiguration::Change::POINTER_SPEED);
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(AllOf(WithMotionAction(HOVER_MOVE),
+                                                                WithDisplayId(DISPLAY_ID),
+                                                                WithRelativeMotion(10, 20)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationOnDisplayChange) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
+    mReaderConfiguration.setDisplayViewports({primaryViewport});
+    // Disable acceleration for the display.
+    mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID);
+    createDevice();
+
+    // Don't associate the device with the display yet.
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID,
+                                                   /*viewport=*/std::nullopt);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+
+    // Verify that acceleration is being applied by default by checking that the movement is scaled.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+    const auto& coords = get<NotifyMotionArgs>(args.back()).pointerCoords[0];
+    ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), 10.f);
+    ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), 20.f);
+
+    // Now associate the device with the display, and verify that acceleration is disabled.
+    deviceContext.setViewport(primaryViewport);
+    args += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithDisplayId(DISPLAY_ID),
+                              WithRelativeMotion(10, 20)))));
+}
+
+namespace {
+
+// Minimum timestamp separation between subsequent input events from a Bluetooth device.
+constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
+// Maximum smoothing time delta so that we don't generate events too far into the future.
+constexpr nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32);
+
+} // namespace
+
+// --- BluetoothCursorInputMapperUnitTest ---
+
+class BluetoothCursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override { SetUpWithBus(BUS_BLUETOOTH); }
+};
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmoothening) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events that come in quick succession, according to their timestamps.
+    for (int i = 0; i < 3; i++) {
+        constexpr static nsecs_t delta = ms2ns(1);
+        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
+        kernelEventTime += delta;
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+        argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmootheningIsCapped) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events with the same timestamp from the kernel.
+    // Ensure that we do not generate events too far into the future.
+    constexpr static int32_t numEvents =
+            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
+    for (int i = 0; i < numEvents; i++) {
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+
+    // By processing more events with the same timestamp, we should not generate events with a
+    // timestamp that is more than the specified max time delta from the timestamp at its injection.
+    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
+    for (int i = 0; i < 3; i++) {
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(cappedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmootheningNotUsed) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
+    // smoothening is not needed, its timestamp is not affected.
+    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
+    expectedEventTime = kernelEventTime;
+
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index 212fceb..daa000f 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -431,6 +431,38 @@
     return -1;
 }
 
+void FakeEventHub::setMtSlotValues(int32_t deviceId, int32_t axis,
+                                   const std::vector<int32_t>& values) {
+    Device* device = getDevice(deviceId);
+    if (!device) {
+        FAIL() << "Missing device";
+    }
+    device->mtSlotValues[axis] = values;
+}
+
+base::Result<std::vector<int32_t>> FakeEventHub::getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                                 size_t slotCount) const {
+    Device* device = getDevice(deviceId);
+    if (!device) {
+        ADD_FAILURE() << "Missing device";
+        return base::ResultError("Missing device", UNKNOWN_ERROR);
+    }
+    const auto& mtSlotValuesIterator = device->mtSlotValues.find(axis);
+    if (mtSlotValuesIterator == device->mtSlotValues.end()) {
+        return base::ResultError("axis not supported", NAME_NOT_FOUND);
+    }
+    const auto& mtSlotValues = mtSlotValuesIterator->second;
+    if (mtSlotValues.size() != slotCount) {
+        ADD_FAILURE() << "MtSlot values specified for " << mtSlotValues.size()
+                      << " slots but expected for " << slotCount << " Slots";
+        return base::ResultError("Slot count mismatch", NAME_NOT_FOUND);
+    }
+    std::vector<int32_t> outValues(slotCount + 1);
+    outValues[0] = axis;
+    std::copy(mtSlotValues.begin(), mtSlotValues.end(), outValues.begin() + 1);
+    return std::move(outValues);
+}
+
 int32_t FakeEventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
     Device* device = getDevice(deviceId);
     if (!device) {
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index 8e06940..f07b344 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -65,6 +65,7 @@
         bool enabled;
         std::optional<RawLayoutInfo> layoutInfo;
         std::string sysfsRootPath;
+        std::unordered_map<int32_t, std::vector<int32_t>> mtSlotValues;
 
         status_t enable() {
             enabled = true;
@@ -154,6 +155,11 @@
                       int32_t value);
     void assertQueueIsEmpty();
     void setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const;
+    // Populate fake slot values to be returned by the getter, size of the values should be equal to
+    // the slot count
+    void setMtSlotValues(int32_t deviceId, int32_t axis, const std::vector<int32_t>& values);
+    base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                       size_t slotCount) const override;
 
 private:
     Device* getDevice(int32_t deviceId) const;
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
new file mode 100644
index 0000000..530416c
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -0,0 +1,469 @@
+/*
+ * Copyright 2024 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 "FakeInputDispatcherPolicy.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+// --- FakeInputDispatcherPolicy ---
+
+void FakeInputDispatcherPolicy::assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
+    assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) {
+        ASSERT_EQ(event.getType(), InputEventType::KEY);
+        EXPECT_EQ(event.getDisplayId(), args.displayId);
+
+        const auto& keyEvent = static_cast<const KeyEvent&>(event);
+        EXPECT_EQ(keyEvent.getEventTime(), args.eventTime);
+        EXPECT_EQ(keyEvent.getAction(), args.action);
+    });
+}
+
+void FakeInputDispatcherPolicy::assertFilterInputEventWasCalled(const NotifyMotionArgs& args,
+                                                                vec2 point) {
+    assertFilterInputEventWasCalledInternal([&](const InputEvent& event) {
+        ASSERT_EQ(event.getType(), InputEventType::MOTION);
+        EXPECT_EQ(event.getDisplayId(), args.displayId);
+
+        const auto& motionEvent = static_cast<const MotionEvent&>(event);
+        EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
+        EXPECT_EQ(motionEvent.getAction(), args.action);
+        EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION);
+        EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION);
+        EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION);
+        EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION);
+    });
+}
+
+void FakeInputDispatcherPolicy::assertFilterInputEventWasNotCalled() {
+    std::scoped_lock lock(mLock);
+    ASSERT_EQ(nullptr, mFilteredEvent);
+}
+
+void FakeInputDispatcherPolicy::assertNotifyConfigurationChangedWasCalled(nsecs_t when) {
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mConfigurationChangedTime) << "Timed out waiting for configuration changed call";
+    ASSERT_EQ(*mConfigurationChangedTime, when);
+    mConfigurationChangedTime = std::nullopt;
+}
+
+void FakeInputDispatcherPolicy::assertNotifySwitchWasCalled(const NotifySwitchArgs& args) {
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mLastNotifySwitch);
+    // We do not check id because it is not exposed to the policy
+    EXPECT_EQ(args.eventTime, mLastNotifySwitch->eventTime);
+    EXPECT_EQ(args.policyFlags, mLastNotifySwitch->policyFlags);
+    EXPECT_EQ(args.switchValues, mLastNotifySwitch->switchValues);
+    EXPECT_EQ(args.switchMask, mLastNotifySwitch->switchMask);
+    mLastNotifySwitch = std::nullopt;
+}
+
+void FakeInputDispatcherPolicy::assertOnPointerDownEquals(const sp<IBinder>& touchedToken) {
+    std::scoped_lock lock(mLock);
+    ASSERT_EQ(touchedToken, mOnPointerDownToken);
+    mOnPointerDownToken.clear();
+}
+
+void FakeInputDispatcherPolicy::assertOnPointerDownWasNotCalled() {
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mOnPointerDownToken == nullptr)
+            << "Expected onPointerDownOutsideFocus to not have been called";
+}
+
+void FakeInputDispatcherPolicy::assertNotifyNoFocusedWindowAnrWasCalled(
+        std::chrono::nanoseconds timeout,
+        const std::shared_ptr<InputApplicationHandle>& expectedApplication) {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    std::shared_ptr<InputApplicationHandle> application;
+    ASSERT_NO_FATAL_FAILURE(
+            application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock));
+    ASSERT_EQ(expectedApplication, application);
+}
+
+void FakeInputDispatcherPolicy::assertNotifyWindowUnresponsiveWasCalled(
+        std::chrono::nanoseconds timeout, const sp<gui::WindowInfoHandle>& window) {
+    LOG_ALWAYS_FATAL_IF(window == nullptr, "window should not be null");
+    assertNotifyWindowUnresponsiveWasCalled(timeout, window->getToken(),
+                                            window->getInfo()->ownerPid);
+}
+
+void FakeInputDispatcherPolicy::assertNotifyWindowUnresponsiveWasCalled(
+        std::chrono::nanoseconds timeout, const sp<IBinder>& expectedToken,
+        std::optional<gui::Pid> expectedPid) {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    AnrResult result;
+    ASSERT_NO_FATAL_FAILURE(result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock));
+    ASSERT_EQ(expectedToken, result.token);
+    ASSERT_EQ(expectedPid, result.pid);
+}
+
+sp<IBinder> FakeInputDispatcherPolicy::getUnresponsiveWindowToken(
+        std::chrono::nanoseconds timeout) {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    AnrResult result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock);
+    const auto& [token, _] = result;
+    return token;
+}
+
+void FakeInputDispatcherPolicy::assertNotifyWindowResponsiveWasCalled(
+        const sp<IBinder>& expectedToken, std::optional<gui::Pid> expectedPid) {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    AnrResult result;
+    ASSERT_NO_FATAL_FAILURE(result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock));
+    ASSERT_EQ(expectedToken, result.token);
+    ASSERT_EQ(expectedPid, result.pid);
+}
+
+sp<IBinder> FakeInputDispatcherPolicy::getResponsiveWindowToken() {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    AnrResult result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock);
+    const auto& [token, _] = result;
+    return token;
+}
+
+void FakeInputDispatcherPolicy::assertNotifyAnrWasNotCalled() {
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mAnrApplications.empty());
+    ASSERT_TRUE(mAnrWindows.empty());
+    ASSERT_TRUE(mResponsiveWindows.empty())
+            << "ANR was not called, but please also consume the 'connection is responsive' "
+               "signal";
+}
+
+PointerCaptureRequest FakeInputDispatcherPolicy::assertSetPointerCaptureCalled(
+        const sp<gui::WindowInfoHandle>& window, bool enabled) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    if (!mPointerCaptureChangedCondition
+                 .wait_for(lock, 100ms, [this, enabled, window]() REQUIRES(mLock) {
+                     if (enabled) {
+                         return mPointerCaptureRequest->isEnable() &&
+                                 mPointerCaptureRequest->window == window->getToken();
+                     } else {
+                         return !mPointerCaptureRequest->isEnable();
+                     }
+                 })) {
+        ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << window->getName() << ", "
+                      << enabled << ") to be called.";
+        return {};
+    }
+    auto request = *mPointerCaptureRequest;
+    mPointerCaptureRequest.reset();
+    return request;
+}
+
+void FakeInputDispatcherPolicy::assertSetPointerCaptureNotCalled() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) {
+        FAIL() << "Expected setPointerCapture(request) to not be called, but was called. "
+                  "enabled = "
+               << std::to_string(mPointerCaptureRequest->isEnable());
+    }
+    mPointerCaptureRequest.reset();
+}
+
+void FakeInputDispatcherPolicy::assertDropTargetEquals(const InputDispatcherInterface& dispatcher,
+                                                       const sp<IBinder>& targetToken) {
+    dispatcher.waitForIdle();
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mNotifyDropWindowWasCalled);
+    ASSERT_EQ(targetToken, mDropTargetWindowToken);
+    mNotifyDropWindowWasCalled = false;
+}
+
+void FakeInputDispatcherPolicy::assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+    std::optional<sp<IBinder>> receivedToken =
+            getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock,
+                                                  mNotifyInputChannelBroken);
+    ASSERT_TRUE(receivedToken.has_value()) << "Did not receive the broken channel token";
+    ASSERT_EQ(token, *receivedToken);
+}
+
+void FakeInputDispatcherPolicy::setInterceptKeyTimeout(std::chrono::milliseconds timeout) {
+    mInterceptKeyTimeout = timeout;
+}
+
+std::chrono::nanoseconds FakeInputDispatcherPolicy::getKeyWaitingForEventsTimeout() {
+    return 500ms;
+}
+
+void FakeInputDispatcherPolicy::setStaleEventTimeout(std::chrono::nanoseconds timeout) {
+    mStaleEventTimeout = timeout;
+}
+
+void FakeInputDispatcherPolicy::assertUserActivityNotPoked() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    std::optional<UserActivityPokeEvent> pokeEvent =
+            getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock,
+                                                  mNotifyUserActivity);
+
+    ASSERT_FALSE(pokeEvent) << "Expected user activity not to have been poked";
+}
+
+void FakeInputDispatcherPolicy::assertUserActivityPoked(
+        std::optional<UserActivityPokeEvent> expectedPokeEvent) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    std::optional<UserActivityPokeEvent> pokeEvent =
+            getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock,
+                                                  mNotifyUserActivity);
+    ASSERT_TRUE(pokeEvent) << "Expected a user poke event";
+
+    if (expectedPokeEvent) {
+        ASSERT_EQ(expectedPokeEvent, *pokeEvent);
+    }
+}
+
+void FakeInputDispatcherPolicy::assertNotifyDeviceInteractionWasCalled(int32_t deviceId,
+                                                                       std::set<gui::Uid> uids) {
+    ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms));
+}
+
+void FakeInputDispatcherPolicy::assertNotifyDeviceInteractionWasNotCalled() {
+    ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms));
+}
+
+void FakeInputDispatcherPolicy::setUnhandledKeyHandler(
+        std::function<std::optional<KeyEvent>(const KeyEvent&)> handler) {
+    std::scoped_lock lock(mLock);
+    mUnhandledKeyHandler = handler;
+}
+
+void FakeInputDispatcherPolicy::assertUnhandledKeyReported(int32_t keycode) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+    std::optional<int32_t> unhandledKeycode =
+            getItemFromStorageLockedInterruptible(100ms, mReportedUnhandledKeycodes, lock,
+                                                  mNotifyUnhandledKey);
+    ASSERT_TRUE(unhandledKeycode) << "Expected unhandled key to be reported";
+    ASSERT_EQ(unhandledKeycode, keycode);
+}
+
+void FakeInputDispatcherPolicy::assertUnhandledKeyNotReported() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+    std::optional<int32_t> unhandledKeycode =
+            getItemFromStorageLockedInterruptible(10ms, mReportedUnhandledKeycodes, lock,
+                                                  mNotifyUnhandledKey);
+    ASSERT_FALSE(unhandledKeycode) << "Expected unhandled key NOT to be reported";
+}
+
+template <class T>
+T FakeInputDispatcherPolicy::getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout,
+                                                            std::queue<T>& storage,
+                                                            std::unique_lock<std::mutex>& lock)
+        REQUIRES(mLock) {
+    // If there is an ANR, Dispatcher won't be idle because there are still events
+    // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle
+    // before checking if ANR was called.
+    // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need
+    // to provide it some time to act. 100ms seems reasonable.
+    std::chrono::duration timeToWait = timeout + 100ms; // provide some slack
+    const std::chrono::time_point start = std::chrono::steady_clock::now();
+    std::optional<T> token =
+            getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr);
+    if (!token.has_value()) {
+        ADD_FAILURE() << "Did not receive the ANR callback";
+        return {};
+    }
+
+    const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
+    // Ensure that the ANR didn't get raised too early. We can't be too strict here because
+    // the dispatcher started counting before this function was called
+    if (std::chrono::abs(timeout - waited) > 100ms) {
+        ADD_FAILURE() << "ANR was raised too early or too late. Expected "
+                      << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()
+                      << "ms, but waited "
+                      << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count()
+                      << "ms instead";
+    }
+    return *token;
+}
+
+template <class T>
+std::optional<T> FakeInputDispatcherPolicy::getItemFromStorageLockedInterruptible(
+        std::chrono::nanoseconds timeout, std::queue<T>& storage,
+        std::unique_lock<std::mutex>& lock, std::condition_variable& condition) REQUIRES(mLock) {
+    condition.wait_for(lock, timeout, [&storage]() REQUIRES(mLock) { return !storage.empty(); });
+    if (storage.empty()) {
+        return std::nullopt;
+    }
+    T item = storage.front();
+    storage.pop();
+    return std::make_optional(item);
+}
+
+void FakeInputDispatcherPolicy::notifyConfigurationChanged(nsecs_t when) {
+    std::scoped_lock lock(mLock);
+    mConfigurationChangedTime = when;
+}
+
+void FakeInputDispatcherPolicy::notifyWindowUnresponsive(const sp<IBinder>& connectionToken,
+                                                         std::optional<gui::Pid> pid,
+                                                         const std::string&) {
+    std::scoped_lock lock(mLock);
+    mAnrWindows.push({connectionToken, pid});
+    mNotifyAnr.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyWindowResponsive(const sp<IBinder>& connectionToken,
+                                                       std::optional<gui::Pid> pid) {
+    std::scoped_lock lock(mLock);
+    mResponsiveWindows.push({connectionToken, pid});
+    mNotifyAnr.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyNoFocusedWindowAnr(
+        const std::shared_ptr<InputApplicationHandle>& applicationHandle) {
+    std::scoped_lock lock(mLock);
+    mAnrApplications.push(applicationHandle);
+    mNotifyAnr.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyInputChannelBroken(const sp<IBinder>& connectionToken) {
+    std::scoped_lock lock(mLock);
+    mBrokenInputChannels.push(connectionToken);
+    mNotifyInputChannelBroken.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) {}
+
+void FakeInputDispatcherPolicy::notifySensorEvent(int32_t deviceId,
+                                                  InputDeviceSensorType sensorType,
+                                                  InputDeviceSensorAccuracy accuracy,
+                                                  nsecs_t timestamp,
+                                                  const std::vector<float>& values) {}
+
+void FakeInputDispatcherPolicy::notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType,
+                                                     InputDeviceSensorAccuracy accuracy) {}
+
+void FakeInputDispatcherPolicy::notifyVibratorState(int32_t deviceId, bool isOn) {}
+
+bool FakeInputDispatcherPolicy::filterInputEvent(const InputEvent& inputEvent,
+                                                 uint32_t policyFlags) {
+    std::scoped_lock lock(mLock);
+    switch (inputEvent.getType()) {
+        case InputEventType::KEY: {
+            const KeyEvent& keyEvent = static_cast<const KeyEvent&>(inputEvent);
+            mFilteredEvent = std::make_unique<KeyEvent>(keyEvent);
+            break;
+        }
+
+        case InputEventType::MOTION: {
+            const MotionEvent& motionEvent = static_cast<const MotionEvent&>(inputEvent);
+            mFilteredEvent = std::make_unique<MotionEvent>(motionEvent);
+            break;
+        }
+        default: {
+            ADD_FAILURE() << "Should only filter keys or motions";
+            break;
+        }
+    }
+    return true;
+}
+
+void FakeInputDispatcherPolicy::interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) {
+    if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) {
+        // Clear intercept state when we handled the event.
+        mInterceptKeyTimeout = 0ms;
+    }
+}
+
+void FakeInputDispatcherPolicy::interceptMotionBeforeQueueing(ui::LogicalDisplayId, uint32_t,
+                                                              int32_t, nsecs_t, uint32_t&) {}
+
+nsecs_t FakeInputDispatcherPolicy::interceptKeyBeforeDispatching(const sp<IBinder>&,
+                                                                 const KeyEvent&, uint32_t) {
+    nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
+    // Clear intercept state so we could dispatch the event in next wake.
+    mInterceptKeyTimeout = 0ms;
+    return delay;
+}
+
+std::optional<KeyEvent> FakeInputDispatcherPolicy::dispatchUnhandledKey(const sp<IBinder>&,
+                                                                        const KeyEvent& event,
+                                                                        uint32_t) {
+    std::scoped_lock lock(mLock);
+    mReportedUnhandledKeycodes.emplace(event.getKeyCode());
+    mNotifyUnhandledKey.notify_all();
+    return mUnhandledKeyHandler != nullptr ? mUnhandledKeyHandler(event) : std::nullopt;
+}
+
+void FakeInputDispatcherPolicy::notifySwitch(nsecs_t when, uint32_t switchValues,
+                                             uint32_t switchMask, uint32_t policyFlags) {
+    std::scoped_lock lock(mLock);
+    // We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
+    // essentially a passthrough for notifySwitch.
+    mLastNotifySwitch =
+            NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask);
+}
+
+void FakeInputDispatcherPolicy::pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                                                 ui::LogicalDisplayId displayId) {
+    std::scoped_lock lock(mLock);
+    mNotifyUserActivity.notify_all();
+    mUserActivityPokeEvents.push({eventTime, eventType, displayId});
+}
+
+bool FakeInputDispatcherPolicy::isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) {
+    return std::chrono::nanoseconds(currentTime - eventTime) >= mStaleEventTimeout;
+}
+
+void FakeInputDispatcherPolicy::onPointerDownOutsideFocus(const sp<IBinder>& newToken) {
+    std::scoped_lock lock(mLock);
+    mOnPointerDownToken = newToken;
+}
+
+void FakeInputDispatcherPolicy::setPointerCapture(const PointerCaptureRequest& request) {
+    std::scoped_lock lock(mLock);
+    mPointerCaptureRequest = {request};
+    mPointerCaptureChangedCondition.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyDropWindow(const sp<IBinder>& token, float x, float y) {
+    std::scoped_lock lock(mLock);
+    mNotifyDropWindowWasCalled = true;
+    mDropTargetWindowToken = token;
+}
+
+void FakeInputDispatcherPolicy::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+                                                        const std::set<gui::Uid>& uids) {
+    ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids));
+}
+
+void FakeInputDispatcherPolicy::assertFilterInputEventWasCalledInternal(
+        const std::function<void(const InputEvent&)>& verify) {
+    std::scoped_lock lock(mLock);
+    ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
+    verify(*mFilteredEvent);
+    mFilteredEvent = nullptr;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index e9d93af..2c86146 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -16,76 +16,191 @@
 
 #pragma once
 
-#include <android-base/logging.h>
 #include "InputDispatcherPolicyInterface.h"
 
-namespace android {
+#include "InputDispatcherInterface.h"
+#include "NotifyArgs.h"
 
-// --- FakeInputDispatcherPolicy ---
+#include <condition_variable>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <queue>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/thread_annotations.h>
+#include <binder/IBinder.h>
+#include <gui/PidUid.h>
+#include <gui/WindowInfo.h>
+#include <input/BlockingQueue.h>
+#include <input/Input.h>
+
+namespace android {
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
 public:
     FakeInputDispatcherPolicy() = default;
     virtual ~FakeInputDispatcherPolicy() = default;
 
+    struct AnrResult {
+        sp<IBinder> token{};
+        std::optional<gui::Pid> pid{};
+    };
+
+    struct UserActivityPokeEvent {
+        nsecs_t eventTime;
+        int32_t eventType;
+        ui::LogicalDisplayId displayId;
+
+        bool operator==(const UserActivityPokeEvent& rhs) const = default;
+        inline friend std::ostream& operator<<(std::ostream& os, const UserActivityPokeEvent& ev) {
+            os << "UserActivityPokeEvent[time=" << ev.eventTime << ", eventType=" << ev.eventType
+               << ", displayId=" << ev.displayId << "]";
+            return os;
+        }
+    };
+
+    void assertFilterInputEventWasCalled(const NotifyKeyArgs& args);
+    void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point);
+    void assertFilterInputEventWasNotCalled();
+    void assertNotifyConfigurationChangedWasCalled(nsecs_t when);
+    void assertNotifySwitchWasCalled(const NotifySwitchArgs& args);
+    void assertOnPointerDownEquals(const sp<IBinder>& touchedToken);
+    void assertOnPointerDownWasNotCalled();
+    /**
+     * This function must be called soon after the expected ANR timer starts,
+     * because we are also checking how much time has passed.
+     */
+    void assertNotifyNoFocusedWindowAnrWasCalled(
+            std::chrono::nanoseconds timeout,
+            const std::shared_ptr<InputApplicationHandle>& expectedApplication);
+    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
+                                                 const sp<gui::WindowInfoHandle>& window);
+    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
+                                                 const sp<IBinder>& expectedToken,
+                                                 std::optional<gui::Pid> expectedPid);
+    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
+    sp<IBinder> getUnresponsiveWindowToken(std::chrono::nanoseconds timeout);
+    void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken,
+                                               std::optional<gui::Pid> expectedPid);
+    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
+    sp<IBinder> getResponsiveWindowToken();
+    void assertNotifyAnrWasNotCalled();
+    PointerCaptureRequest assertSetPointerCaptureCalled(const sp<gui::WindowInfoHandle>& window,
+                                                        bool enabled);
+    void assertSetPointerCaptureNotCalled();
+    void assertDropTargetEquals(const InputDispatcherInterface& dispatcher,
+                                const sp<IBinder>& targetToken);
+    void assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token);
+    /**
+     * Set policy timeout. A value of zero means next key will not be intercepted.
+     */
+    void setInterceptKeyTimeout(std::chrono::milliseconds timeout);
+    std::chrono::nanoseconds getKeyWaitingForEventsTimeout() override;
+    void setStaleEventTimeout(std::chrono::nanoseconds timeout);
+    void assertUserActivityNotPoked();
+    /**
+     * Asserts that a user activity poke has happened. The earliest recorded poke event will be
+     * cleared after this call.
+     *
+     * If an expected UserActivityPokeEvent is provided, asserts that the given event is the
+     * earliest recorded poke event.
+     */
+    void assertUserActivityPoked(std::optional<UserActivityPokeEvent> expectedPokeEvent = {});
+    void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<gui::Uid> uids);
+    void assertNotifyDeviceInteractionWasNotCalled();
+    void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler);
+    void assertUnhandledKeyReported(int32_t keycode);
+    void assertUnhandledKeyNotReported();
+
 private:
-    void notifyConfigurationChanged(nsecs_t) override {}
+    std::mutex mLock;
+    std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
+    std::optional<nsecs_t> mConfigurationChangedTime GUARDED_BY(mLock);
+    sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
+    std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
 
-    void notifyNoFocusedWindowAnr(
-            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
-        LOG(ERROR) << "There is no focused window for " << applicationHandle->getName();
-    }
+    std::condition_variable mPointerCaptureChangedCondition;
 
+    std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock);
+    // ANR handling
+    std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
+    std::queue<AnrResult> mAnrWindows GUARDED_BY(mLock);
+    std::queue<AnrResult> mResponsiveWindows GUARDED_BY(mLock);
+    std::condition_variable mNotifyAnr;
+    std::queue<sp<IBinder>> mBrokenInputChannels GUARDED_BY(mLock);
+    std::condition_variable mNotifyInputChannelBroken;
+
+    sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock);
+    bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false;
+
+    std::condition_variable mNotifyUserActivity;
+    std::queue<UserActivityPokeEvent> mUserActivityPokeEvents;
+
+    std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
+
+    std::chrono::nanoseconds mStaleEventTimeout = 1000ms;
+
+    BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions;
+
+    std::condition_variable mNotifyUnhandledKey;
+    std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock);
+    std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock);
+
+    /**
+     * All three ANR-related callbacks behave the same way, so we use this generic function to wait
+     * for a specific container to become non-empty. When the container is non-empty, return the
+     * first entry from the container and erase it.
+     */
+    template <class T>
+    T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue<T>& storage,
+                                     std::unique_lock<std::mutex>& lock) REQUIRES(mLock);
+
+    template <class T>
+    std::optional<T> getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout,
+                                                           std::queue<T>& storage,
+                                                           std::unique_lock<std::mutex>& lock,
+                                                           std::condition_variable& condition)
+            REQUIRES(mLock);
+
+    void notifyConfigurationChanged(nsecs_t when) override;
     void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
-                                  const std::string& reason) override {
-        LOG(ERROR) << "Window is not responding: " << reason;
-    }
-
+                                  const std::string&) override;
     void notifyWindowResponsive(const sp<IBinder>& connectionToken,
-                                std::optional<gui::Pid> pid) override {}
-
-    void notifyInputChannelBroken(const sp<IBinder>&) override {}
-
-    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
-
+                                std::optional<gui::Pid> pid) override;
+    void notifyNoFocusedWindowAnr(
+            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override;
+    void notifyInputChannelBroken(const sp<IBinder>& connectionToken) override;
+    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override;
     void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
                            InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
-                           const std::vector<float>& values) override {}
+                           const std::vector<float>& values) override;
+    void notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType,
+                              InputDeviceSensorAccuracy accuracy) override;
+    void notifyVibratorState(int32_t deviceId, bool isOn) override;
+    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
+    void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override;
+    void interceptMotionBeforeQueueing(ui::LogicalDisplayId, uint32_t, int32_t, nsecs_t,
+                                       uint32_t&) override;
+    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override;
+    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event,
+                                                 uint32_t) override;
+    void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
+                      uint32_t policyFlags) override;
+    void pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                          ui::LogicalDisplayId displayId) override;
+    bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override;
+    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override;
+    void setPointerCapture(const PointerCaptureRequest& request) override;
+    void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
+    void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+                                 const std::set<gui::Uid>& uids) override;
 
-    void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
-                              InputDeviceSensorAccuracy accuracy) override {}
-
-    void notifyVibratorState(int32_t deviceId, bool isOn) override {}
-
-    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
-        return true; // dispatch event normally
-    }
-
-    void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
-
-    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
-
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
-        return 0;
-    }
-
-    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
-                                                 uint32_t) override {
-        return {};
-    }
-
-    void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
-
-    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
-
-    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
-
-    void setPointerCapture(const PointerCaptureRequest&) override {}
-
-    void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
-
-    void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
-                                 const std::set<gui::Uid>& uids) override {}
+    void assertFilterInputEventWasCalledInternal(
+            const std::function<void(const InputEvent&)>& verify);
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 88f514f..d2cb0ac 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -41,15 +41,21 @@
 }
 
 void FakeInputReaderPolicy::assertStylusGestureNotified(int32_t deviceId) {
-    std::scoped_lock lock(mLock);
-    ASSERT_TRUE(mStylusGestureNotified);
-    ASSERT_EQ(deviceId, *mStylusGestureNotified);
-    mStylusGestureNotified.reset();
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    const bool success =
+            mStylusGestureNotifiedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
+                return mDeviceIdOfNotifiedStylusGesture.has_value();
+            });
+    ASSERT_TRUE(success) << "Timed out waiting for stylus gesture to be notified";
+    ASSERT_EQ(deviceId, *mDeviceIdOfNotifiedStylusGesture);
+    mDeviceIdOfNotifiedStylusGesture.reset();
 }
 
 void FakeInputReaderPolicy::assertStylusGestureNotNotified() {
     std::scoped_lock lock(mLock);
-    ASSERT_FALSE(mStylusGestureNotified);
+    ASSERT_FALSE(mDeviceIdOfNotifiedStylusGesture);
 }
 
 void FakeInputReaderPolicy::clearViewports() {
@@ -76,9 +82,9 @@
     mConfig.setDisplayViewports(mViewports);
 }
 
-void FakeInputReaderPolicy::addDisplayViewport(int32_t displayId, int32_t width, int32_t height,
-                                               ui::Rotation orientation, bool isActive,
-                                               const std::string& uniqueId,
+void FakeInputReaderPolicy::addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width,
+                                               int32_t height, ui::Rotation orientation,
+                                               bool isActive, const std::string& uniqueId,
                                                std::optional<uint8_t> physicalPort,
                                                ViewportType type) {
     const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270;
@@ -123,7 +129,7 @@
 
 void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort,
                                                     uint8_t displayPort) {
-    mConfig.portAssociations.insert({inputPort, displayPort});
+    mConfig.inputPortToDisplayPortAssociations.insert({inputPort, displayPort});
 }
 
 void FakeInputReaderPolicy::addDeviceTypeAssociation(const std::string& inputPort,
@@ -133,7 +139,7 @@
 
 void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
                                                         const std::string& displayUniqueId) {
-    mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
+    mConfig.inputPortToDisplayUniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
 }
 
 void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
@@ -149,11 +155,6 @@
     mConfig.disabledDevices.erase(deviceId);
 }
 
-void FakeInputReaderPolicy::setPointerController(
-        std::shared_ptr<FakePointerController> controller) {
-    mPointerController = std::move(controller);
-}
-
 const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const {
     return mConfig;
 }
@@ -172,16 +173,12 @@
     transform = t;
 }
 
-PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(bool enabled) {
-    mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
+PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(const sp<IBinder>& window) {
+    mConfig.pointerCaptureRequest = {window, mNextPointerCaptureSequenceNumber++};
     return mConfig.pointerCaptureRequest;
 }
 
-void FakeInputReaderPolicy::setShowTouches(bool enabled) {
-    mConfig.showTouches = enabled;
-}
-
-void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) {
+void FakeInputReaderPolicy::setDefaultPointerDisplayId(ui::LogicalDisplayId pointerDisplayId) {
     mConfig.defaultPointerDisplayId = pointerDisplayId;
 }
 
@@ -222,11 +219,6 @@
     *outConfig = mConfig;
 }
 
-std::shared_ptr<PointerControllerInterface> FakeInputReaderPolicy::obtainPointerController(
-        int32_t /*deviceId*/) {
-    return mPointerController;
-}
-
 void FakeInputReaderPolicy::notifyInputDevicesChanged(
         const std::vector<InputDeviceInfo>& inputDevices) {
     std::scoped_lock lock(mLock);
@@ -258,12 +250,13 @@
 
 void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) {
     std::scoped_lock lock(mLock);
-    mStylusGestureNotified = deviceId;
+    mDeviceIdOfNotifiedStylusGesture = deviceId;
+    mStylusGestureNotifiedCondition.notify_all();
 }
 
 std::optional<DisplayViewport> FakeInputReaderPolicy::getPointerViewportForAssociatedDisplay(
-        int32_t associatedDisplayId) {
-    if (associatedDisplayId == ADISPLAY_ID_NONE) {
+        ui::LogicalDisplayId associatedDisplayId) {
+    if (!associatedDisplayId.isValid()) {
         associatedDisplayId = mConfig.defaultPointerDisplayId;
     }
     for (auto& viewport : mViewports) {
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 4ef9c3e..94f1311 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -26,7 +26,6 @@
 #include <InputDevice.h>
 #include <InputReaderBase.h>
 
-#include "FakePointerController.h"
 #include "input/DisplayViewport.h"
 #include "input/InputDevice.h"
 
@@ -49,7 +48,7 @@
     std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
     std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const;
     void addDisplayViewport(DisplayViewport viewport);
-    void addDisplayViewport(int32_t displayId, int32_t width, int32_t height,
+    void addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
                             ui::Rotation orientation, bool isActive, const std::string& uniqueId,
                             std::optional<uint8_t> physicalPort, ViewportType type);
     bool updateViewport(const DisplayViewport& viewport);
@@ -62,15 +61,13 @@
                                       const KeyboardLayoutInfo& layoutInfo);
     void addDisabledDevice(int32_t deviceId);
     void removeDisabledDevice(int32_t deviceId);
-    void setPointerController(std::shared_ptr<FakePointerController> controller);
     const InputReaderConfiguration& getReaderConfiguration() const;
     const std::vector<InputDeviceInfo> getInputDevices() const;
     TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
                                                            ui::Rotation surfaceRotation);
     void setTouchAffineTransformation(const TouchAffineTransformation t);
-    PointerCaptureRequest setPointerCapture(bool enabled);
-    void setShowTouches(bool enabled);
-    void setDefaultPointerDisplayId(int32_t pointerDisplayId);
+    PointerCaptureRequest setPointerCapture(const sp<IBinder>& window);
+    void setDefaultPointerDisplayId(ui::LogicalDisplayId pointerDisplayId);
     void setPointerGestureEnabled(bool enabled);
     float getPointerGestureMovementSpeedRatio();
     float getPointerGestureZoomSpeedRatio();
@@ -80,12 +77,10 @@
     void setIsInputMethodConnectionActive(bool active);
     bool isInputMethodConnectionActive() override;
     std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
-            int32_t associatedDisplayId) override;
+            ui::LogicalDisplayId associatedDisplayId) override;
 
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(
-            int32_t /*deviceId*/) override;
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) override;
@@ -97,14 +92,15 @@
     std::condition_variable mDevicesChangedCondition;
 
     InputReaderConfiguration mConfig;
-    std::shared_ptr<FakePointerController> mPointerController;
     std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
     bool mInputDevicesChanged GUARDED_BY(mLock){false};
     std::vector<DisplayViewport> mViewports;
     TouchAffineTransformation transform;
-    std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){};
     bool mIsInputMethodConnectionActive{false};
 
+    std::condition_variable mStylusGestureNotifiedCondition;
+    std::optional<DeviceId> mDeviceIdOfNotifiedStylusGesture GUARDED_BY(mLock){};
+
     uint32_t mNextPointerCaptureSequenceNumber{0};
 };
 
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp
new file mode 100644
index 0000000..b46055e
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2024 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 "FakeInputTracingBackend.h"
+
+#include <android-base/logging.h>
+#include <utils/Errors.h>
+
+namespace android::inputdispatcher {
+
+namespace {
+
+// Use a larger timeout while waiting for events to be traced, compared to the timeout used while
+// waiting to receive events through the input channel. Events are traced from a separate thread,
+// which does not have the same high thread priority as the InputDispatcher's thread, so the tracer
+// is expected to lag behind the Dispatcher at times.
+constexpr auto TRACE_TIMEOUT = std::chrono::seconds(5);
+
+base::ResultError<> error(const std::ostringstream& ss) {
+    return base::ResultError(ss.str(), BAD_VALUE);
+}
+
+inline auto getId(const trace::TracedEvent& v) {
+    return std::visit([](const auto& event) { return event.id; }, v);
+}
+
+MotionEvent toInputEvent(const trace::TracedMotionEvent& e,
+                         const trace::WindowDispatchArgs& dispatchArgs,
+                         const std::array<uint8_t, 32>& hmac) {
+    MotionEvent traced;
+    traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action, e.actionButton,
+                      dispatchArgs.resolvedFlags, e.edgeFlags, e.metaState, e.buttonState,
+                      e.classification, dispatchArgs.transform, e.xPrecision, e.yPrecision,
+                      e.xCursorPosition, e.yCursorPosition, dispatchArgs.rawTransform, e.downTime,
+                      e.eventTime, e.pointerProperties.size(), e.pointerProperties.data(),
+                      e.pointerCoords.data());
+    return traced;
+}
+
+KeyEvent toInputEvent(const trace::TracedKeyEvent& e, const trace::WindowDispatchArgs& dispatchArgs,
+                      const std::array<uint8_t, 32>& hmac) {
+    KeyEvent traced;
+    traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action,
+                      dispatchArgs.resolvedFlags, e.keyCode, e.scanCode, e.metaState,
+                      dispatchArgs.resolvedKeyRepeatCount, e.downTime, e.eventTime);
+    return traced;
+}
+
+} // namespace
+
+// --- VerifyingTrace ---
+
+void VerifyingTrace::expectKeyDispatchTraced(const KeyEvent& event, int32_t windowId) {
+    std::scoped_lock lock(mLock);
+    mExpectedEvents.emplace_back(event, windowId);
+}
+
+void VerifyingTrace::expectMotionDispatchTraced(const MotionEvent& event, int32_t windowId) {
+    std::scoped_lock lock(mLock);
+    mExpectedEvents.emplace_back(event, windowId);
+}
+
+void VerifyingTrace::verifyExpectedEventsTraced() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    // Poll for all expected events to be traced, and keep track of the latest poll result.
+    base::Result<void> result;
+    mEventTracedCondition.wait_for(lock, TRACE_TIMEOUT, [&]() REQUIRES(mLock) {
+        for (const auto& [expectedEvent, windowId] : mExpectedEvents) {
+            std::visit([&](const auto& event)
+                               REQUIRES(mLock) { result = verifyEventTraced(event, windowId); },
+                       expectedEvent);
+            if (!result.ok()) {
+                return false;
+            }
+        }
+        return true;
+    });
+
+    EXPECT_TRUE(result.ok())
+            << "Timed out waiting for all expected events to be traced successfully: "
+            << result.error().message();
+}
+
+void VerifyingTrace::reset() {
+    std::scoped_lock lock(mLock);
+    mTracedEvents.clear();
+    mTracedWindowDispatches.clear();
+    mExpectedEvents.clear();
+}
+
+template <typename Event>
+base::Result<void> VerifyingTrace::verifyEventTraced(const Event& expectedEvent,
+                                                     int32_t expectedWindowId) const {
+    std::ostringstream msg;
+
+    auto tracedEventsIt = mTracedEvents.find(expectedEvent.getId());
+    if (tracedEventsIt == mTracedEvents.end()) {
+        msg << "Expected event with ID 0x" << std::hex << expectedEvent.getId()
+            << " to be traced, but it was not.\n"
+            << "Expected event: " << expectedEvent;
+        return error(msg);
+    }
+
+    auto tracedDispatchesIt =
+            std::find_if(mTracedWindowDispatches.begin(), mTracedWindowDispatches.end(),
+                         [&](const trace::WindowDispatchArgs& args) {
+                             return args.windowId == expectedWindowId &&
+                                     getId(args.eventEntry) == expectedEvent.getId();
+                         });
+    if (tracedDispatchesIt == mTracedWindowDispatches.end()) {
+        msg << "Expected dispatch of event with ID 0x" << std::hex << expectedEvent.getId()
+            << " to window with ID 0x" << expectedWindowId << " to be traced, but it was not.\n"
+            << "Expected event: " << expectedEvent;
+        return error(msg);
+    }
+
+    // Verify that the traced event matches the expected event exactly.
+    return std::visit(
+            [&](const auto& traced) -> base::Result<void> {
+                Event tracedEvent;
+                using T = std::decay_t<decltype(traced)>;
+                if constexpr (std::is_same_v<Event, MotionEvent> &&
+                              std::is_same_v<T, trace::TracedMotionEvent>) {
+                    tracedEvent =
+                            toInputEvent(traced, *tracedDispatchesIt, expectedEvent.getHmac());
+                } else if constexpr (std::is_same_v<Event, KeyEvent> &&
+                                     std::is_same_v<T, trace::TracedKeyEvent>) {
+                    tracedEvent =
+                            toInputEvent(traced, *tracedDispatchesIt, expectedEvent.getHmac());
+                } else {
+                    msg << "Received the wrong event type!\n"
+                        << "Expected event: " << expectedEvent;
+                    return error(msg);
+                }
+
+                const auto result = testing::internal::CmpHelperEQ("expectedEvent", "tracedEvent",
+                                                                   expectedEvent, tracedEvent);
+                if (!result) {
+                    msg << result.failure_message();
+                    return error(msg);
+                }
+                return {};
+            },
+            tracedEventsIt->second);
+}
+
+// --- FakeInputTracingBackend ---
+
+void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event,
+                                            const trace::TracedEventMetadata&) {
+    {
+        std::scoped_lock lock(mTrace->mLock);
+        mTrace->mTracedEvents.emplace(event.id, event);
+    }
+    mTrace->mEventTracedCondition.notify_all();
+}
+
+void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event,
+                                               const trace::TracedEventMetadata&) {
+    {
+        std::scoped_lock lock(mTrace->mLock);
+        mTrace->mTracedEvents.emplace(event.id, event);
+    }
+    mTrace->mEventTracedCondition.notify_all();
+}
+
+void FakeInputTracingBackend::traceWindowDispatch(const trace::WindowDispatchArgs& args,
+                                                  const trace::TracedEventMetadata&) {
+    {
+        std::scoped_lock lock(mTrace->mLock);
+        mTrace->mTracedWindowDispatches.push_back(args);
+    }
+    mTrace->mEventTracedCondition.notify_all();
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h
new file mode 100644
index 0000000..cd4b507
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputTracingBackend.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 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 "../dispatcher/trace/InputTracingBackendInterface.h"
+
+#include <android-base/result.h>
+#include <android-base/thread_annotations.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+#include <vector>
+
+namespace android::inputdispatcher {
+
+/**
+ * A class representing an input trace, used to make assertions on what was traced by
+ * InputDispatcher in tests. This class is thread-safe.
+ */
+class VerifyingTrace {
+public:
+    VerifyingTrace() = default;
+
+    /** Add an expectation for a key event to be traced. */
+    void expectKeyDispatchTraced(const KeyEvent& event, int32_t windowId);
+
+    /** Add an expectation for a motion event to be traced. */
+    void expectMotionDispatchTraced(const MotionEvent& event, int32_t windowId);
+
+    /**
+     * Wait and verify that all expected events are traced.
+     * This is a lenient verifier that does not expect the events to be traced in the order
+     * that the events were expected, and does not fail if there are events that are traced that
+     * were not expected. Verifying does not clear the expectations.
+     */
+    void verifyExpectedEventsTraced();
+
+    /** Reset the trace and clear all expectations. */
+    void reset();
+
+private:
+    std::mutex mLock;
+    std::condition_variable mEventTracedCondition;
+    std::unordered_map<uint32_t /*eventId*/, trace::TracedEvent> mTracedEvents GUARDED_BY(mLock);
+    std::vector<trace::WindowDispatchArgs> mTracedWindowDispatches GUARDED_BY(mLock);
+    std::vector<std::pair<std::variant<KeyEvent, MotionEvent>, int32_t /*windowId*/>>
+            mExpectedEvents GUARDED_BY(mLock);
+
+    friend class FakeInputTracingBackend;
+
+    // Helper to verify that the given event appears as expected in the trace. If the verification
+    // fails, the error message describes why.
+    template <typename Event>
+    base::Result<void> verifyEventTraced(const Event&, int32_t windowId) const REQUIRES(mLock);
+};
+
+/**
+ * A backend implementation for input tracing that records events to the provided
+ * VerifyingTrace used for testing.
+ */
+class FakeInputTracingBackend : public trace::InputTracingBackendInterface {
+public:
+    FakeInputTracingBackend(std::shared_ptr<VerifyingTrace> trace) : mTrace(trace) {}
+
+private:
+    std::shared_ptr<VerifyingTrace> mTrace;
+
+    void traceKeyEvent(const trace::TracedKeyEvent& entry,
+                       const trace::TracedEventMetadata&) override;
+    void traceMotionEvent(const trace::TracedMotionEvent& entry,
+                          const trace::TracedEventMetadata&) override;
+    void traceWindowDispatch(const trace::WindowDispatchArgs& entry,
+                             const trace::TracedEventMetadata&) override;
+};
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 80319f2..2bb57b3 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -28,22 +28,32 @@
     mMaxY = maxY;
 }
 
-const std::map<int32_t, std::vector<int32_t>>& FakePointerController::getSpots() {
+void FakePointerController::clearBounds() {
+    mHaveBounds = false;
+}
+
+const std::map<ui::LogicalDisplayId, std::vector<int32_t>>& FakePointerController::getSpots() {
     return mSpotsByDisplay;
 }
 
 void FakePointerController::setPosition(float x, float y) {
+    if (!mEnabled) return;
+
     mX = x;
     mY = y;
 }
 
 FloatPoint FakePointerController::getPosition() const {
+    if (!mEnabled) {
+        return {0, 0};
+    }
+
     return {mX, mY};
 }
 
-int32_t FakePointerController::getDisplayId() const {
-    if (!mDisplayId) {
-        return ADISPLAY_ID_NONE;
+ui::LogicalDisplayId FakePointerController::getDisplayId() const {
+    if (!mEnabled || !mDisplayId) {
+        return ui::LogicalDisplayId::INVALID;
     }
     return *mDisplayId;
 }
@@ -60,11 +70,21 @@
 }
 
 void FakePointerController::setCustomPointerIcon(const SpriteIcon& icon) {
+    if (!mEnabled) return;
+
     ASSERT_FALSE(mCustomIconStyle.has_value()) << "Custom pointer icon was set more than once";
     mCustomIconStyle = icon.style;
 }
 
-void FakePointerController::assertViewportSet(int32_t displayId) {
+void FakePointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) {
+    if (skip) {
+        mDisplaysToSkipScreenshot.insert(displayId);
+    } else {
+        mDisplaysToSkipScreenshot.erase(displayId);
+    }
+};
+
+void FakePointerController::assertViewportSet(ui::LogicalDisplayId displayId) {
     ASSERT_TRUE(mDisplayId);
     ASSERT_EQ(displayId, mDisplayId);
 }
@@ -79,7 +99,7 @@
     ASSERT_NEAR(y, actualY, 1);
 }
 
-void FakePointerController::assertSpotCount(int32_t displayId, int32_t count) {
+void FakePointerController::assertSpotCount(ui::LogicalDisplayId displayId, int32_t count) {
     auto it = mSpotsByDisplay.find(displayId);
     ASSERT_TRUE(it != mSpotsByDisplay.end()) << "Spots not found for display " << displayId;
     ASSERT_EQ(static_cast<size_t>(count), it->second.size());
@@ -105,15 +125,28 @@
     ASSERT_EQ(std::nullopt, mCustomIconStyle);
 }
 
+void FakePointerController::assertIsHiddenOnMirroredDisplays(ui::LogicalDisplayId displayId,
+                                                             bool isHidden) {
+    if (isHidden) {
+        ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end());
+    } else {
+        ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) == mDisplaysToSkipScreenshot.end());
+    }
+}
+
 bool FakePointerController::isPointerShown() {
     return mIsPointerShown;
 }
 
 std::optional<FloatRect> FakePointerController::getBounds() const {
+    if (!mEnabled) return std::nullopt;
+
     return mHaveBounds ? std::make_optional<FloatRect>(mMinX, mMinY, mMaxX, mMaxY) : std::nullopt;
 }
 
 void FakePointerController::move(float deltaX, float deltaY) {
+    if (!mEnabled) return;
+
     mX += deltaX;
     if (mX < mMinX) mX = mMinX;
     if (mX > mMaxX) mX = mMaxX;
@@ -123,14 +156,20 @@
 }
 
 void FakePointerController::fade(Transition) {
+    if (!mEnabled) return;
+
     mIsPointerShown = false;
 }
 void FakePointerController::unfade(Transition) {
+    if (!mEnabled) return;
+
     mIsPointerShown = true;
 }
 
 void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
-                                     int32_t displayId) {
+                                     ui::LogicalDisplayId displayId) {
+    if (!mEnabled) return;
+
     std::vector<int32_t> newSpots;
     // Add spots for fingers that are down.
     for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
@@ -142,6 +181,8 @@
 }
 
 void FakePointerController::clearSpots() {
+    if (!mEnabled) return;
+
     mSpotsByDisplay.clear();
 }
 
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 800f864..5bc713f 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -17,10 +17,10 @@
 #pragma once
 
 #include <PointerControllerInterface.h>
-#include <gui/constants.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <utils/BitSet.h>
+#include <unordered_set>
 
 namespace android {
 
@@ -30,27 +30,33 @@
 
 class FakePointerController : public PointerControllerInterface {
 public:
+    FakePointerController() : FakePointerController(/*enabled=*/true) {}
+    FakePointerController(bool enabled) : mEnabled(enabled) {}
+
     virtual ~FakePointerController() {}
 
     void setBounds(float minX, float minY, float maxX, float maxY);
-    const std::map<int32_t, std::vector<int32_t>>& getSpots();
+    void clearBounds();
+    const std::map<ui::LogicalDisplayId, std::vector<int32_t>>& getSpots();
 
     void setPosition(float x, float y) override;
     FloatPoint getPosition() const override;
-    int32_t getDisplayId() const override;
+    ui::LogicalDisplayId getDisplayId() const override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
     void updatePointerIcon(PointerIconStyle iconId) override;
     void setCustomPointerIcon(const SpriteIcon& icon) override;
+    void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override;
     void fade(Transition) override;
 
-    void assertViewportSet(int32_t displayId);
+    void assertViewportSet(ui::LogicalDisplayId displayId);
     void assertViewportNotSet();
     void assertPosition(float x, float y);
-    void assertSpotCount(int32_t displayId, int32_t count);
+    void assertSpotCount(ui::LogicalDisplayId displayId, int32_t count);
     void assertPointerIconSet(PointerIconStyle iconId);
     void assertPointerIconNotSet();
     void assertCustomPointerIconSet(PointerIconStyle iconId);
     void assertCustomPointerIconNotSet();
+    void assertIsHiddenOnMirroredDisplays(ui::LogicalDisplayId displayId, bool isHidden);
     bool isPointerShown();
 
 private:
@@ -60,18 +66,20 @@
     void unfade(Transition) override;
     void setPresentation(Presentation) override {}
     void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
-                  int32_t displayId) override;
+                  ui::LogicalDisplayId displayId) override;
     void clearSpots() override;
 
+    const bool mEnabled;
     bool mHaveBounds{false};
     float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0};
     float mX{0}, mY{0};
-    std::optional<int32_t> mDisplayId;
+    std::optional<ui::LogicalDisplayId> mDisplayId;
     bool mIsPointerShown{false};
     std::optional<PointerIconStyle> mIconStyle;
     std::optional<PointerIconStyle> mCustomIconStyle;
 
-    std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
+    std::map<ui::LogicalDisplayId, std::vector<int32_t>> mSpotsByDisplay;
+    std::unordered_set<ui::LogicalDisplayId> mDisplaysToSkipScreenshot;
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h
deleted file mode 100644
index fe25130..0000000
--- a/services/inputflinger/tests/FakeWindowHandle.h
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <android-base/logging.h>
-#include "../dispatcher/InputDispatcher.h"
-
-using android::base::Result;
-using android::gui::Pid;
-using android::gui::TouchOcclusionMode;
-using android::gui::Uid;
-using android::gui::WindowInfo;
-using android::gui::WindowInfoHandle;
-
-namespace android {
-namespace inputdispatcher {
-
-namespace {
-
-// The default pid and uid for windows created by the test.
-constexpr gui::Pid WINDOW_PID{999};
-constexpr gui::Uid WINDOW_UID{1001};
-
-static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
-
-} // namespace
-
-class FakeInputReceiver {
-public:
-    std::unique_ptr<InputEvent> consumeEvent(std::chrono::milliseconds timeout) {
-        uint32_t consumeSeq = 0;
-        std::unique_ptr<InputEvent> event;
-
-        std::chrono::time_point start = std::chrono::steady_clock::now();
-        status_t result = WOULD_BLOCK;
-        while (result == WOULD_BLOCK) {
-            InputEvent* rawEventPtr = nullptr;
-            result = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
-                                       &rawEventPtr);
-            event = std::unique_ptr<InputEvent>(rawEventPtr);
-            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
-            if (elapsed > timeout) {
-                if (timeout != 0ms) {
-                    LOG(ERROR) << "Waited too long for consumer to produce an event, giving up";
-                }
-                break;
-            }
-        }
-        // Events produced by this factory are owned pointers.
-        if (result != OK) {
-            if (timeout == 0ms) {
-                // This is likely expected. No need to log.
-            } else {
-                LOG(ERROR) << "Received result =  " << result << " from consume";
-            }
-            return nullptr;
-        }
-        result = mConsumer.sendFinishedSignal(consumeSeq, true);
-        if (result != OK) {
-            LOG(ERROR) << "Received result = " << result << " from sendFinishedSignal";
-        }
-        return event;
-    }
-
-    explicit FakeInputReceiver(std::unique_ptr<InputChannel> channel, const std::string name)
-          : mConsumer(std::move(channel)) {}
-
-    virtual ~FakeInputReceiver() {}
-
-private:
-    std::unique_ptr<InputChannel> mClientChannel;
-    InputConsumer mConsumer;
-    DynamicInputEventFactory mEventFactory;
-};
-
-class FakeWindowHandle : public WindowInfoHandle {
-public:
-    static const int32_t WIDTH = 600;
-    static const int32_t HEIGHT = 800;
-
-    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-                     InputDispatcher& dispatcher, const std::string name, int32_t displayId)
-          : mName(name) {
-        Result<std::unique_ptr<InputChannel>> channel = dispatcher.createInputChannel(name);
-        mInfo.token = (*channel)->getConnectionToken();
-        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
-
-        inputApplicationHandle->updateInfo();
-        mInfo.applicationInfo = *inputApplicationHandle->getInfo();
-
-        mInfo.id = sId++;
-        mInfo.name = name;
-        mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
-        mInfo.alpha = 1.0;
-        mInfo.frame.left = 0;
-        mInfo.frame.top = 0;
-        mInfo.frame.right = WIDTH;
-        mInfo.frame.bottom = HEIGHT;
-        mInfo.transform.set(0, 0);
-        mInfo.globalScaleFactor = 1.0;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
-        mInfo.ownerPid = WINDOW_PID;
-        mInfo.ownerUid = WINDOW_UID;
-        mInfo.displayId = displayId;
-        mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
-    }
-
-    sp<FakeWindowHandle> clone(int32_t displayId) {
-        sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
-        handle->mInfo = mInfo;
-        handle->mInfo.displayId = displayId;
-        handle->mInfo.id = sId++;
-        handle->mInputReceiver = mInputReceiver;
-        return handle;
-    }
-
-    void setTouchable(bool touchable) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
-    }
-
-    void setFocusable(bool focusable) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
-    }
-
-    void setVisible(bool visible) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
-    }
-
-    void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
-        mInfo.dispatchingTimeout = timeout;
-    }
-
-    void setPaused(bool paused) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused);
-    }
-
-    void setPreventSplitting(bool preventSplitting) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting);
-    }
-
-    void setSlippery(bool slippery) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery);
-    }
-
-    void setWatchOutsideTouch(bool watchOutside) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
-    }
-
-    void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
-
-    void setInterceptsStylus(bool interceptsStylus) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
-    }
-
-    void setDropInput(bool dropInput) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
-    }
-
-    void setDropInputIfObscured(bool dropInputIfObscured) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
-    }
-
-    void setNoInputChannel(bool noInputChannel) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
-    }
-
-    void setDisableUserActivity(bool disableUserActivity) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
-    }
-
-    void setAlpha(float alpha) { mInfo.alpha = alpha; }
-
-    void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
-
-    void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
-
-    void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
-        mInfo.frame.left = frame.left;
-        mInfo.frame.top = frame.top;
-        mInfo.frame.right = frame.right;
-        mInfo.frame.bottom = frame.bottom;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(frame);
-
-        const Rect logicalDisplayFrame = displayTransform.transform(frame);
-        ui::Transform translate;
-        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
-        mInfo.transform = translate * displayTransform;
-    }
-
-    void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
-
-    void setIsWallpaper(bool isWallpaper) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper);
-    }
-
-    void setDupTouchToWallpaper(bool hasWallpaper) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
-    }
-
-    void setTrustedOverlay(bool trustedOverlay) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
-    }
-
-    void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
-        mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
-    }
-
-    void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); }
-
-    void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
-
-    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout) {
-        if (mInputReceiver == nullptr) {
-            return nullptr;
-        }
-        return mInputReceiver->consumeEvent(timeout);
-    }
-
-    void consumeMotion() {
-        std::unique_ptr<InputEvent> event = consume(100ms);
-
-        if (event == nullptr) {
-            LOG(FATAL) << mName << ": expected a MotionEvent, but didn't get one.";
-            return;
-        }
-
-        if (event->getType() != InputEventType::MOTION) {
-            LOG(FATAL) << mName << " expected a MotionEvent, got " << *event;
-            return;
-        }
-    }
-
-    sp<IBinder> getToken() { return mInfo.token; }
-
-    const std::string& getName() { return mName; }
-
-    void setOwnerInfo(Pid ownerPid, Uid ownerUid) {
-        mInfo.ownerPid = ownerPid;
-        mInfo.ownerUid = ownerUid;
-    }
-
-    Pid getPid() const { return mInfo.ownerPid; }
-
-    void destroyReceiver() { mInputReceiver = nullptr; }
-
-private:
-    FakeWindowHandle(std::string name) : mName(name){};
-    const std::string mName;
-    std::shared_ptr<FakeInputReceiver> mInputReceiver;
-    static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
-    friend class sp<FakeWindowHandle>;
-};
-
-std::atomic<int32_t> FakeWindowHandle::sId{1};
-
-} // namespace inputdispatcher
-
-} // namespace android
diff --git a/services/inputflinger/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp
new file mode 100644
index 0000000..b116521
--- /dev/null
+++ b/services/inputflinger/tests/FakeWindows.cpp
@@ -0,0 +1,359 @@
+/*
+ * Copyright 2024 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 "FakeWindows.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+// --- FakeInputReceiver ---
+
+FakeInputReceiver::FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel,
+                                     const std::string name)
+      : mConsumer(std::move(clientChannel)), mName(name) {}
+
+std::unique_ptr<InputEvent> FakeInputReceiver::consume(std::chrono::milliseconds timeout,
+                                                       bool handled) {
+    auto [consumeSeq, event] = receiveEvent(timeout);
+    if (!consumeSeq) {
+        return nullptr;
+    }
+    finishEvent(*consumeSeq, handled);
+    return std::move(event);
+}
+
+std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> FakeInputReceiver::receiveEvent(
+        std::chrono::milliseconds timeout) {
+    uint32_t consumeSeq;
+    std::unique_ptr<InputEvent> event;
+
+    std::chrono::time_point start = std::chrono::steady_clock::now();
+    status_t status = WOULD_BLOCK;
+    while (status == WOULD_BLOCK) {
+        InputEvent* rawEventPtr = nullptr;
+        status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
+                                   &rawEventPtr);
+        event = std::unique_ptr<InputEvent>(rawEventPtr);
+        std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
+        if (elapsed > timeout) {
+            break;
+        }
+    }
+
+    if (status == WOULD_BLOCK) {
+        // Just means there's no event available.
+        return std::make_pair(std::nullopt, nullptr);
+    }
+
+    if (status != OK) {
+        ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK.";
+        return std::make_pair(std::nullopt, nullptr);
+    }
+    if (event == nullptr) {
+        ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer";
+    }
+    return std::make_pair(consumeSeq, std::move(event));
+}
+
+void FakeInputReceiver::finishEvent(uint32_t consumeSeq, bool handled) {
+    const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled);
+    ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
+}
+
+void FakeInputReceiver::sendTimeline(int32_t inputEventId,
+                                     std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
+    const status_t status = mConsumer.sendTimeline(inputEventId, timeline);
+    ASSERT_EQ(OK, status);
+}
+
+void FakeInputReceiver::consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
+                                     std::optional<ui::LogicalDisplayId> expectedDisplayId,
+                                     std::optional<int32_t> expectedFlags) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(expectedEventType, event->getType())
+            << mName.c_str() << " expected " << ftl::enum_string(expectedEventType)
+            << " event, got " << *event;
+
+    if (expectedDisplayId.has_value()) {
+        EXPECT_EQ(expectedDisplayId, event->getDisplayId());
+    }
+
+    switch (expectedEventType) {
+        case InputEventType::KEY: {
+            const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
+            ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
+            if (expectedFlags.has_value()) {
+                EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
+            }
+            break;
+        }
+        case InputEventType::MOTION: {
+            const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
+            ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
+            if (expectedFlags.has_value()) {
+                EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
+            }
+            break;
+        }
+        case InputEventType::FOCUS: {
+            FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
+        }
+        case InputEventType::CAPTURE: {
+            FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
+        }
+        case InputEventType::TOUCH_MODE: {
+            FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events";
+        }
+        case InputEventType::DRAG: {
+            FAIL() << "Use 'consumeDragEvent' for DRAG events";
+        }
+    }
+}
+
+std::unique_ptr<MotionEvent> FakeInputReceiver::consumeMotion() {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+
+    if (event == nullptr) {
+        ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one.";
+        return nullptr;
+    }
+
+    if (event->getType() != InputEventType::MOTION) {
+        ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event;
+        return nullptr;
+    }
+    return std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
+}
+
+void FakeInputReceiver::consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+    std::unique_ptr<MotionEvent> motionEvent = consumeMotion();
+    ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher;
+    ASSERT_THAT(*motionEvent, matcher);
+}
+
+void FakeInputReceiver::consumeFocusEvent(bool hasFocus, bool inTouchMode) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(InputEventType::FOCUS, event->getType()) << "Instead of FocusEvent, got " << *event;
+
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId())
+            << mName.c_str() << ": event displayId should always be NONE.";
+
+    FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
+    EXPECT_EQ(hasFocus, focusEvent.getHasFocus());
+}
+
+void FakeInputReceiver::consumeCaptureEvent(bool hasCapture) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(InputEventType::CAPTURE, event->getType())
+            << "Instead of CaptureEvent, got " << *event;
+
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId())
+            << mName.c_str() << ": event displayId should always be NONE.";
+
+    const auto& captureEvent = static_cast<const CaptureEvent&>(*event);
+    EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled());
+}
+
+void FakeInputReceiver::consumeDragEvent(bool isExiting, float x, float y) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event;
+
+    EXPECT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId())
+            << mName.c_str() << ": event displayId should always be NONE.";
+
+    const auto& dragEvent = static_cast<const DragEvent&>(*event);
+    EXPECT_EQ(isExiting, dragEvent.isExiting());
+    EXPECT_EQ(x, dragEvent.getX());
+    EXPECT_EQ(y, dragEvent.getY());
+}
+
+void FakeInputReceiver::consumeTouchModeEvent(bool inTouchMode) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType())
+            << "Instead of TouchModeEvent, got " << *event;
+
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId())
+            << mName.c_str() << ": event displayId should always be NONE.";
+    const auto& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
+    EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode());
+}
+
+void FakeInputReceiver::assertNoEvents(std::chrono::milliseconds timeout) {
+    std::unique_ptr<InputEvent> event = consume(timeout);
+    if (event == nullptr) {
+        return;
+    }
+    if (event->getType() == InputEventType::KEY) {
+        KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
+        ADD_FAILURE() << "Received key event " << keyEvent;
+    } else if (event->getType() == InputEventType::MOTION) {
+        MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
+        ADD_FAILURE() << "Received motion event " << motionEvent;
+    } else if (event->getType() == InputEventType::FOCUS) {
+        FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
+        ADD_FAILURE() << "Received focus event, hasFocus = "
+                      << (focusEvent.getHasFocus() ? "true" : "false");
+    } else if (event->getType() == InputEventType::CAPTURE) {
+        const auto& captureEvent = static_cast<CaptureEvent&>(*event);
+        ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
+                      << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
+    } else if (event->getType() == InputEventType::TOUCH_MODE) {
+        const auto& touchModeEvent = static_cast<TouchModeEvent&>(*event);
+        ADD_FAILURE() << "Received touch mode event, inTouchMode = "
+                      << (touchModeEvent.isInTouchMode() ? "true" : "false");
+    }
+    FAIL() << mName.c_str()
+           << ": should not have received any events, so consume() should return NULL";
+}
+
+sp<IBinder> FakeInputReceiver::getToken() {
+    return mConsumer.getChannel()->getConnectionToken();
+}
+
+int FakeInputReceiver::getChannelFd() {
+    return mConsumer.getChannel()->getFd();
+}
+
+// --- FakeWindowHandle ---
+
+std::function<void(const std::unique_ptr<InputEvent>&, const gui::WindowInfo&)>
+        FakeWindowHandle::sOnEventReceivedCallback{};
+
+std::atomic<int32_t> FakeWindowHandle::sId{1};
+
+FakeWindowHandle::FakeWindowHandle(
+        const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
+        const std::unique_ptr<inputdispatcher::InputDispatcher>& dispatcher, const std::string name,
+        ui::LogicalDisplayId displayId, bool createInputChannel)
+      : mName(name) {
+    sp<IBinder> token;
+    if (createInputChannel) {
+        base::Result<std::unique_ptr<InputChannel>> channel = dispatcher->createInputChannel(name);
+        token = (*channel)->getConnectionToken();
+        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
+    }
+
+    inputApplicationHandle->updateInfo();
+    mInfo.applicationInfo = *inputApplicationHandle->getInfo();
+
+    mInfo.token = token;
+    mInfo.id = sId++;
+    mInfo.name = name;
+    mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
+    mInfo.alpha = 1.0;
+    mInfo.frame = Rect(0, 0, WIDTH, HEIGHT);
+    mInfo.transform.set(0, 0);
+    mInfo.globalScaleFactor = 1.0;
+    mInfo.touchableRegion.clear();
+    mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
+    mInfo.ownerPid = WINDOW_PID;
+    mInfo.ownerUid = WINDOW_UID;
+    mInfo.displayId = displayId;
+    mInfo.inputConfig = InputConfig::DEFAULT;
+}
+
+sp<FakeWindowHandle> FakeWindowHandle::clone(ui::LogicalDisplayId displayId) {
+    sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
+    handle->mInfo = mInfo;
+    handle->mInfo.displayId = displayId;
+    handle->mInfo.id = sId++;
+    handle->mInputReceiver = mInputReceiver;
+    return handle;
+}
+
+std::unique_ptr<KeyEvent> FakeWindowHandle::consumeKey(bool handled) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
+    if (event == nullptr) {
+        ADD_FAILURE() << "No event";
+        return nullptr;
+    }
+    if (event->getType() != InputEventType::KEY) {
+        ADD_FAILURE() << "Instead of key event, got " << event;
+        return nullptr;
+    }
+    return std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event.release()));
+}
+
+std::unique_ptr<MotionEvent> FakeWindowHandle::consumeMotionEvent(
+        const ::testing::Matcher<MotionEvent>& matcher) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    if (event == nullptr) {
+        std::ostringstream matcherDescription;
+        matcher.DescribeTo(&matcherDescription);
+        ADD_FAILURE() << "No event (expected " << matcherDescription.str() << ") on " << mName;
+        return nullptr;
+    }
+    if (event->getType() != InputEventType::MOTION) {
+        ADD_FAILURE() << "Instead of motion event, got " << *event << " on " << mName;
+        return nullptr;
+    }
+    std::unique_ptr<MotionEvent> motionEvent =
+            std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
+    if (motionEvent == nullptr) {
+        return nullptr;
+    }
+    EXPECT_THAT(*motionEvent, matcher) << " on " << mName;
+    return motionEvent;
+}
+
+void FakeWindowHandle::assertNoEvents(std::optional<std::chrono::milliseconds> timeout) {
+    if (mInputReceiver == nullptr && mInfo.inputConfig.test(InputConfig::NO_INPUT_CHANNEL)) {
+        return; // Can't receive events if the window does not have input channel
+    }
+    ASSERT_NE(nullptr, mInputReceiver)
+            << "Window without InputReceiver must specify feature NO_INPUT_CHANNEL";
+    mInputReceiver->assertNoEvents(timeout.value_or(CONSUME_TIMEOUT_NO_EVENT_EXPECTED));
+}
+
+std::unique_ptr<InputEvent> FakeWindowHandle::consume(std::chrono::milliseconds timeout,
+                                                      bool handled) {
+    if (mInputReceiver == nullptr) {
+        LOG(FATAL) << "Cannot consume event from a window with no input event receiver";
+    }
+    std::unique_ptr<InputEvent> event = mInputReceiver->consume(timeout, handled);
+    if (event == nullptr) {
+        ADD_FAILURE() << "Consume failed: no event";
+    }
+
+    if (sOnEventReceivedCallback != nullptr) {
+        sOnEventReceivedCallback(event, mInfo);
+    }
+    return event;
+}
+
+std::pair<std::optional<uint32_t /*seq*/>, std::unique_ptr<InputEvent>>
+FakeWindowHandle::receive() {
+    if (mInputReceiver == nullptr) {
+        ADD_FAILURE() << "Invalid receive event on window with no receiver";
+        return std::make_pair(std::nullopt, nullptr);
+    }
+    auto out = mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    const auto& [_, event] = out;
+
+    if (sOnEventReceivedCallback != nullptr) {
+        sOnEventReceivedCallback(event, mInfo);
+    }
+    return out;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
new file mode 100644
index 0000000..36a8f00
--- /dev/null
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -0,0 +1,410 @@
+/*
+ * Copyright 2024 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 "../dispatcher/InputDispatcher.h"
+#include "TestEventMatchers.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/InputConsumer.h>
+
+namespace android {
+
+/**
+ * If we expect to receive the event, the timeout can be made very long. When the test are running
+ * correctly, we will actually never wait until the end of the timeout because the wait will end
+ * when the event comes in. Still, this value shouldn't be infinite. During development, a local
+ * change may cause the test to fail. This timeout should be short enough to not annoy so that the
+ * developer can see the failure quickly (on human scale).
+ */
+static constexpr std::chrono::duration CONSUME_TIMEOUT_EVENT_EXPECTED = 1000ms;
+
+/**
+ * When no event is expected, we can have a very short timeout. A large value here would slow down
+ * the tests. In the unlikely event of system being too slow, the event may still be present but the
+ * timeout would complete before it is consumed. This would result in test flakiness. If this
+ * occurs, the flakiness rate would be high. Since the flakes are treated with high priority, this
+ * would get noticed and addressed quickly.
+ */
+static constexpr std::chrono::duration CONSUME_TIMEOUT_NO_EVENT_EXPECTED = 10ms;
+
+/**
+ * The default pid and uid for windows created on the primary display by the test.
+ */
+static constexpr gui::Pid WINDOW_PID{999};
+static constexpr gui::Uid WINDOW_UID{1001};
+
+/**
+ * Default input dispatching timeout if there is no focused application or paused window
+ * from which to determine an appropriate dispatching timeout.
+ */
+static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds(
+        android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+        android::base::HwTimeoutMultiplier());
+
+// --- FakeInputReceiver ---
+
+class FakeInputReceiver {
+public:
+    explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name);
+
+    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = false);
+    /**
+     * Receive an event without acknowledging it.
+     * Return the sequence number that could later be used to send finished signal.
+     */
+    std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent(
+            std::chrono::milliseconds timeout);
+    /**
+     * To be used together with "receiveEvent" to complete the consumption of an event.
+     */
+    void finishEvent(uint32_t consumeSeq, bool handled = true);
+
+    void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+
+    void consumeEvent(android::InputEventType expectedEventType, int32_t expectedAction,
+                      std::optional<ui::LogicalDisplayId> expectedDisplayId,
+                      std::optional<int32_t> expectedFlags);
+
+    std::unique_ptr<MotionEvent> consumeMotion();
+    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher);
+
+    void consumeFocusEvent(bool hasFocus, bool inTouchMode);
+    void consumeCaptureEvent(bool hasCapture);
+    void consumeDragEvent(bool isExiting, float x, float y);
+    void consumeTouchModeEvent(bool inTouchMode);
+
+    void assertNoEvents(std::chrono::milliseconds timeout);
+
+    sp<IBinder> getToken();
+    int getChannelFd();
+
+private:
+    InputConsumer mConsumer;
+    DynamicInputEventFactory mEventFactory;
+    std::string mName;
+};
+
+// --- FakeWindowHandle ---
+
+class FakeWindowHandle : public gui::WindowInfoHandle {
+public:
+    static const int32_t WIDTH = 600;
+    static const int32_t HEIGHT = 800;
+    using InputConfig = gui::WindowInfo::InputConfig;
+
+    // This is a callback that is fired when an event is received by the window.
+    // It is static to avoid having to pass it individually into all of the FakeWindowHandles
+    // created by tests.
+    // TODO(b/210460522): Update the tests to use a factory pattern so that we can avoid
+    //   the need to make this static.
+    static std::function<void(const std::unique_ptr<InputEvent>&, const gui::WindowInfo&)>
+            sOnEventReceivedCallback;
+
+    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
+                     const std::unique_ptr<inputdispatcher::InputDispatcher>& dispatcher,
+                     const std::string name, ui::LogicalDisplayId displayId,
+                     bool createInputChannel = true);
+
+    sp<FakeWindowHandle> clone(ui::LogicalDisplayId displayId);
+
+    inline void setTouchable(bool touchable) {
+        mInfo.setInputConfig(InputConfig::NOT_TOUCHABLE, !touchable);
+    }
+
+    inline void setFocusable(bool focusable) {
+        mInfo.setInputConfig(InputConfig::NOT_FOCUSABLE, !focusable);
+    }
+
+    inline void setVisible(bool visible) {
+        mInfo.setInputConfig(InputConfig::NOT_VISIBLE, !visible);
+    }
+
+    inline void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
+        mInfo.dispatchingTimeout = timeout;
+    }
+
+    inline void setPaused(bool paused) {
+        mInfo.setInputConfig(InputConfig::PAUSE_DISPATCHING, paused);
+    }
+
+    inline void setPreventSplitting(bool preventSplitting) {
+        mInfo.setInputConfig(InputConfig::PREVENT_SPLITTING, preventSplitting);
+    }
+
+    inline void setSlippery(bool slippery) {
+        mInfo.setInputConfig(InputConfig::SLIPPERY, slippery);
+    }
+
+    inline void setWatchOutsideTouch(bool watchOutside) {
+        mInfo.setInputConfig(InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
+    }
+
+    inline void setSpy(bool spy) { mInfo.setInputConfig(InputConfig::SPY, spy); }
+
+    inline void setSecure(bool secure) {
+        if (secure) {
+            mInfo.layoutParamsFlags |= gui::WindowInfo::Flag::SECURE;
+        } else {
+            using namespace ftl::flag_operators;
+            mInfo.layoutParamsFlags &= ~gui::WindowInfo::Flag::SECURE;
+        }
+        mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_PRIVACY, secure);
+    }
+
+    inline void setInterceptsStylus(bool interceptsStylus) {
+        mInfo.setInputConfig(InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
+    }
+
+    inline void setDropInput(bool dropInput) {
+        mInfo.setInputConfig(InputConfig::DROP_INPUT, dropInput);
+    }
+
+    inline void setDropInputIfObscured(bool dropInputIfObscured) {
+        mInfo.setInputConfig(InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
+    }
+
+    inline void setNoInputChannel(bool noInputChannel) {
+        mInfo.setInputConfig(InputConfig::NO_INPUT_CHANNEL, noInputChannel);
+    }
+
+    inline void setDisableUserActivity(bool disableUserActivity) {
+        mInfo.setInputConfig(InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
+    }
+
+    inline void setGlobalStylusBlocksTouch(bool shouldGlobalStylusBlockTouch) {
+        mInfo.setInputConfig(InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH, shouldGlobalStylusBlockTouch);
+    }
+
+    inline void setAlpha(float alpha) { mInfo.alpha = alpha; }
+
+    inline void setTouchOcclusionMode(gui::TouchOcclusionMode mode) {
+        mInfo.touchOcclusionMode = mode;
+    }
+
+    inline void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
+
+    inline void setFrame(const Rect& frame,
+                         const ui::Transform& displayTransform = ui::Transform()) {
+        mInfo.frame = frame;
+        mInfo.touchableRegion.clear();
+        mInfo.addTouchableRegion(frame);
+
+        const Rect logicalDisplayFrame = displayTransform.transform(frame);
+        ui::Transform translate;
+        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
+        mInfo.transform = translate * displayTransform;
+    }
+
+    inline void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
+
+    inline void setIsWallpaper(bool isWallpaper) {
+        mInfo.setInputConfig(InputConfig::IS_WALLPAPER, isWallpaper);
+    }
+
+    inline void setDupTouchToWallpaper(bool hasWallpaper) {
+        mInfo.setInputConfig(InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
+    }
+
+    inline void setTrustedOverlay(bool trustedOverlay) {
+        mInfo.setInputConfig(InputConfig::TRUSTED_OVERLAY, trustedOverlay);
+    }
+
+    inline void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
+        mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
+    }
+
+    inline void setWindowScale(float xScale, float yScale) {
+        setWindowTransform(xScale, 0, 0, yScale);
+    }
+
+    inline void setWindowOffset(float offsetX, float offsetY) {
+        mInfo.transform.set(offsetX, offsetY);
+    }
+
+    std::unique_ptr<KeyEvent> consumeKey(bool handled = true);
+
+    inline std::unique_ptr<KeyEvent> consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
+        std::unique_ptr<KeyEvent> keyEvent = consumeKey();
+        EXPECT_NE(nullptr, keyEvent);
+        if (!keyEvent) {
+            return nullptr;
+        }
+        EXPECT_THAT(*keyEvent, matcher);
+        return keyEvent;
+    }
+
+    inline void consumeKeyDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
+        consumeKeyEvent(testing::AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                       WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
+    }
+
+    inline void consumeKeyUp(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
+        consumeKeyEvent(testing::AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                       WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionCancel(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
+    }
+
+    inline void consumeMotionMove(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionDown(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeAnyMotionDown(expectedDisplayId, expectedFlags);
+    }
+
+    inline void consumeAnyMotionDown(
+            std::optional<ui::LogicalDisplayId> expectedDisplayId = std::nullopt,
+            std::optional<int32_t> expectedFlags = std::nullopt) {
+        consumeMotionEvent(
+                testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                               testing::Conditional(expectedDisplayId.has_value(),
+                                                    WithDisplayId(*expectedDisplayId), testing::_),
+                               testing::Conditional(expectedFlags.has_value(),
+                                                    WithFlags(*expectedFlags), testing::_)));
+    }
+
+    inline void consumeMotionPointerDown(
+            int32_t pointerIdx,
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        consumeMotionEvent(testing::AllOf(WithMotionAction(action),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionPointerUp(
+            int32_t pointerIdx,
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        consumeMotionEvent(testing::AllOf(WithMotionAction(action),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionUp(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionOutside(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionOutsideWithZeroedCoords() {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE),
+                                          WithRawCoords(0, 0)));
+    }
+
+    inline void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
+        ASSERT_NE(mInputReceiver, nullptr)
+                << "Cannot consume events from a window with no receiver";
+        mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode);
+    }
+
+    inline void consumeCaptureEvent(bool hasCapture) {
+        ASSERT_NE(mInputReceiver, nullptr)
+                << "Cannot consume events from a window with no receiver";
+        mInputReceiver->consumeCaptureEvent(hasCapture);
+    }
+
+    std::unique_ptr<MotionEvent> consumeMotionEvent(
+            const ::testing::Matcher<MotionEvent>& matcher = testing::_);
+
+    inline void consumeDragEvent(bool isExiting, float x, float y) {
+        mInputReceiver->consumeDragEvent(isExiting, x, y);
+    }
+
+    inline void consumeTouchModeEvent(bool inTouchMode) {
+        ASSERT_NE(mInputReceiver, nullptr)
+                << "Cannot consume events from a window with no receiver";
+        mInputReceiver->consumeTouchModeEvent(inTouchMode);
+    }
+
+    inline std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent() {
+        return receive();
+    }
+
+    inline void finishEvent(uint32_t sequenceNum) {
+        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
+        mInputReceiver->finishEvent(sequenceNum);
+    }
+
+    inline void sendTimeline(int32_t inputEventId,
+                             std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
+        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
+        mInputReceiver->sendTimeline(inputEventId, timeline);
+    }
+
+    void assertNoEvents(std::optional<std::chrono::milliseconds> timeout = {});
+
+    inline sp<IBinder> getToken() { return mInfo.token; }
+
+    inline const std::string& getName() { return mName; }
+
+    inline void setOwnerInfo(gui::Pid ownerPid, gui::Uid ownerUid) {
+        mInfo.ownerPid = ownerPid;
+        mInfo.ownerUid = ownerUid;
+    }
+
+    inline gui::Pid getPid() const { return mInfo.ownerPid; }
+
+    inline void destroyReceiver() { mInputReceiver = nullptr; }
+
+    inline int getChannelFd() { return mInputReceiver->getChannelFd(); }
+
+    // FakeWindowHandle uses this consume method to ensure received events are added to the trace.
+    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = true);
+
+private:
+    FakeWindowHandle(std::string name) : mName(name){};
+    const std::string mName;
+    std::shared_ptr<FakeInputReceiver> mInputReceiver;
+    static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
+    friend class sp<FakeWindowHandle>;
+
+    // FakeWindowHandle uses this receive method to ensure received events are added to the trace.
+    std::pair<std::optional<uint32_t /*seq*/>, std::unique_ptr<InputEvent>> receive();
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index 2ff9c3c..f794da5 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -20,6 +20,7 @@
 
 #define ASSERT_FOCUS_CHANGE(_changes, _oldFocus, _newFocus) \
     {                                                       \
+        ASSERT_TRUE(_changes.has_value());                  \
         ASSERT_EQ(_oldFocus, _changes->oldFocus);           \
         ASSERT_EQ(_newFocus, _changes->newFocus);           \
     }
@@ -73,7 +74,7 @@
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
-    ASSERT_EQ(request.displayId, changes->displayId);
+    ASSERT_EQ(ui::LogicalDisplayId{request.displayId}, changes->displayId);
 
     // invisible window cannot get focused
     request.token = invisibleWindowToken;
@@ -152,6 +153,39 @@
     ASSERT_FOCUS_CHANGE(changes, /*from*/ invisibleWindowToken, /*to*/ nullptr);
 }
 
+TEST(FocusResolverTest, FocusTransferToMirror) {
+    sp<IBinder> focusableWindowToken = sp<BBinder>::make();
+    auto window = sp<FakeWindowHandle>::make("Window", focusableWindowToken,
+                                             /*focusable=*/true, /*visible=*/true);
+    auto mirror = sp<FakeWindowHandle>::make("Mirror", focusableWindowToken,
+                                             /*focusable=*/true, /*visible=*/true);
+
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = focusableWindowToken;
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, {window, mirror});
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
+
+    // The mirror window now comes on top, and the focus does not change
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId},
+                                            {mirror, window});
+    ASSERT_FALSE(changes.has_value());
+
+    // The window now comes on top while the mirror is removed, and the focus does not change
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {window});
+    ASSERT_FALSE(changes.has_value());
+
+    // The window is removed but the mirror is on top, and focus does not change
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {mirror});
+    ASSERT_FALSE(changes.has_value());
+
+    // All windows removed
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {});
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
+}
+
 TEST(FocusResolverTest, SetInputWindows) {
     sp<IBinder> focusableWindowToken = sp<BBinder>::make();
     std::vector<sp<WindowInfoHandle>> windows;
@@ -169,9 +203,13 @@
             focusResolver.setFocusedWindow(request, windows);
     ASSERT_EQ(focusableWindowToken, changes->newFocus);
 
+    // When there are no changes to the window, focus does not change
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
+    ASSERT_FALSE(changes.has_value());
+
     // Window visibility changes and the window loses focus
     window->setVisible(false);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
 }
 
@@ -195,7 +233,7 @@
 
     // Window visibility changes and the window gets focused
     invisibleWindow->setVisible(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ invisibleWindowToken);
 }
 
@@ -219,25 +257,25 @@
 
     // Focusability changes and the window gets focused
     window->setFocusable(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
 
     // Visibility changes and the window loses focus
     window->setVisible(false);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
 
     // Visibility changes and the window gets focused
     window->setVisible(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
 
     // Window is gone and the window loses focus
-    changes = focusResolver.setInputWindows(request.displayId, {});
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {});
     ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
 
     // Window returns and the window gains focus
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
 }
 
@@ -270,27 +308,27 @@
 
     // Embedded is now focusable so will gain focus
     embeddedWindow->setFocusable(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken);
 
     // Embedded is not visible so host will get focus
     embeddedWindow->setVisible(false);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken);
 
     // Embedded is now visible so will get focus
     embeddedWindow->setVisible(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken);
 
     // Remove focusTransferTarget from host. Host will gain focus.
     hostWindow->editInfo()->focusTransferTarget = nullptr;
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken);
 
     // Set invalid token for focusTransferTarget. Host will remain focus
     hostWindow->editInfo()->focusTransferTarget = sp<BBinder>::make();
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FALSE(changes);
 }
 
@@ -378,21 +416,16 @@
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
-    ASSERT_EQ(request.displayId, changes->displayId);
-
-    // Start with a focused window
-    window->setFocusable(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
-    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+    ASSERT_EQ(ui::LogicalDisplayId{request.displayId}, changes->displayId);
 
     // When a display is removed, all windows are removed from the display
     // and our focused window loses focus
-    changes = focusResolver.setInputWindows(request.displayId, {});
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {});
     ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
-    focusResolver.displayRemoved(request.displayId);
+    focusResolver.displayRemoved(ui::LogicalDisplayId{request.displayId});
 
-    // When a display is readded, the window does not get focus since the request was cleared.
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    // When a display is re-added, the window does not get focus since the request was cleared.
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FALSE(changes);
 }
 
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index d2b68dd..d0cd677 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -20,7 +20,6 @@
 #include <flag_macros.h>
 #include <gestures/GestureConverter.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
@@ -41,18 +40,21 @@
 
 const auto TOUCHPAD_PALM_REJECTION =
         ACONFIG_FLAG(input_flags, enable_touchpad_typing_palm_rejection);
+const auto TOUCHPAD_PALM_REJECTION_V2 =
+        ACONFIG_FLAG(input_flags, enable_v2_touchpad_typing_palm_rejection);
 
 } // namespace
 
 using testing::AllOf;
+using testing::Each;
+using testing::ElementsAre;
+using testing::VariantWith;
 
-class GestureConverterTestBase : public testing::Test {
+class GestureConverterTest : public testing::Test {
 protected:
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr int32_t EVENTHUB_ID = 1;
     static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2;
-    static constexpr float POINTER_X = 500;
-    static constexpr float POINTER_Y = 200;
 
     void SetUp() {
         mFakeEventHub = std::make_unique<FakeEventHub>();
@@ -63,11 +65,6 @@
         mDevice = newDevice();
         mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, -500, 500, 0, 0, 20);
         mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 500, 0, 0, 20);
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-        mFakePointerController->setPosition(POINTER_X, POINTER_Y);
-        mFakePolicy->setPointerController(mFakePointerController);
     }
 
     std::shared_ptr<InputDevice> newDevice() {
@@ -90,239 +87,253 @@
     std::unique_ptr<TestInputListener> mFakeListener;
     std::unique_ptr<InstrumentedInputReader> mReader;
     std::shared_ptr<InputDevice> mDevice;
-    std::shared_ptr<FakePointerController> mFakePointerController;
-};
-
-class GestureConverterTest : public GestureConverterTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(false);
-        GestureConverterTestBase::SetUp();
-    }
 };
 
 TEST_F(GestureConverterTest, Move) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithRelativeMotion(0, 0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithRelativeMotion(-5, 10), WithButtonState(0),
+                                          WithPressure(0.0f)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+    // The same gesture again should only repeat the HOVER_MOVE, not the HOVER_ENTER.
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Move_Rotated) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithRelativeMotion(0, 0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithRelativeMotion(10, 5), WithButtonState(0),
+                                          WithPressure(0.0f)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ButtonsChange) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     // Press left and right buttons at once
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
                         /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Then release the left button
     Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
                           /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                              WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                              WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Finally release the right button
     Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
                            /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
-    ASSERT_EQ(3u, args.size());
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+}
 
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE,
+                        /*is_tap=*/false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args.front(),
+                VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0),
+                              WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT))));
 }
 
 TEST_F(GestureConverterTest, DragWithButton) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     // Press the button
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
                         /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Move
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Release the button
     Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                       /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
                       /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll) {
     const nsecs_t downTime = 12345;
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y - 10),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithDownTime(downTime))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(0, -10),
+                                          WithGestureScrollDistance(0, 10, EPSILON)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y - 15),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X, POINTER_Y - 15),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, -15),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_Rotated) {
@@ -330,132 +341,182 @@
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 10, POINTER_Y),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithDownTime(downTime))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(-10, 0),
+                                          WithGestureScrollDistance(0, 10, EPSILON)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 15, POINTER_Y),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X - 15, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(-15, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionClassification(MotionClassification::NONE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::NONE),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like pinch.
     Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
 }
 
+TEST_F(GestureConverterTest, Scroll_ClearsFakeFingerPositionOnSubsequentScrollGestures) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 15, -10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -2, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    Gesture flingGestureEnd(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 0,
+                            GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGestureEnd);
+
+    // Start a second scoll gesture, and ensure the fake finger is reset to (0, 0), instead of
+    // continuing from the position where the last scroll gesture's fake finger ended.
+    Gesture secondScrollStart(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 2,
+                              14);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, secondScrollStart);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(2, 14),
+                                          WithGestureScrollDistance(-2, -14, EPSILON)))));
+}
+
 TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5,
                         /*dy=*/10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
                          /*dy=*/5);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like pinch.
     Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
@@ -468,52 +529,46 @@
     // only checks movement in one dimension.
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                              WithGestureSwipeFingerCount(3), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
     // move appropriately.
     NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithPointerCount(1u)));
     PointerCoords finger0Start = arg.pointerCoords[0];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
     PointerCoords finger1Start = arg.pointerCoords[1];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
     PointerCoords finger2Start = arg.pointerCoords[2];
     args.pop_front();
 
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
@@ -523,7 +578,7 @@
 
     Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -531,7 +586,7 @@
                       WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
                       WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
@@ -540,73 +595,84 @@
     EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ui::LogicalDisplayId::DEFAULT))));
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
     // move appropriately.
     NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithPointerCount(1u)));
     PointerCoords finger0Start = arg.pointerCoords[0];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
     PointerCoords finger1Start = arg.pointerCoords[1];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
     PointerCoords finger2Start = arg.pointerCoords[2];
     args.pop_front();
 
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10);
@@ -616,13 +682,13 @@
 
     Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
@@ -631,84 +697,77 @@
     EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ui::LogicalDisplayId::DEFAULT))));
 }
 
 TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                          /* dx= */ 10, /* dy= */ 0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(5u, args.size());
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                              WithGestureSwipeFingerCount(4), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Four fake fingers should be created. We don't actually care where they are, so long as they
     // move appropriately.
     NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithPointerCount(1u)));
     PointerCoords finger0Start = arg.pointerCoords[0];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
     PointerCoords finger1Start = arg.pointerCoords[1];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
     PointerCoords finger2Start = arg.pointerCoords[2];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(4u)));
     PointerCoords finger3Start = arg.pointerCoords[3];
     args.pop_front();
 
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithGestureOffset(0.01, 0, EPSILON), WithPointerCount(4u)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10);
@@ -720,7 +779,7 @@
 
     Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 5, /* dy= */ 0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -728,7 +787,7 @@
                       WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
                       WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
@@ -739,194 +798,226 @@
     EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(4u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(4u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_Inwards) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(-100, 0), WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(1.0f, EPSILON),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(0.8f, EPSILON),
-                      WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
-                      WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(0.8f, EPSILON),
+                              WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_Outwards) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(-100, 0), WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(1.0f, EPSILON),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.2f, EPSILON),
-                      WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
-                      WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                          /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(1.1f, EPSILON),
+                              WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like scroll.
     Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
                           /*dy=*/0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
 }
@@ -934,276 +1025,314 @@
 TEST_F(GestureConverterTest, ResetWithButtonPressed) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
                         /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringScroll) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X, POINTER_Y - 10),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, -10),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringPinch) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, FlingTapDown) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
 
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+}
 
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y));
-    ASSERT_TRUE(mFakePointerController->isPointerShown());
+TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    input_flags::enable_touchpad_fling_stop(true);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture);
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+
+    Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                           /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE)))));
 }
 
 TEST_F(GestureConverterTest, Tap) {
     // Tap should produce button press/release events
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                        /* down= */ GESTURES_BUTTON_LEFT,
                        /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture);
 
-    ASSERT_EQ(5u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithButtonState(0), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0), WithPressure(0.0f)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Click) {
     // Click should produce button press/release events
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
 
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithButtonState(0), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
 
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithPressure(0.0f)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(0), WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION),
+                  REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
     // Tap should be ignored when disabled
     mReader->getContext()->setPreventingTouchpadTaps(true);
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-
-    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
                        /* down= */ GESTURES_BUTTON_LEFT,
                        /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
 
     // no events should be generated
     ASSERT_EQ(0u, args.size());
@@ -1212,6 +1341,90 @@
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
+TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabledWithDelay,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
+    // Tap should be ignored when disabled
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
+
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+
+    // taps before the threshold should still be ignored
+    currentTime += TAP_ENABLE_DELAY_NANOS.count();
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // taps after the threshold should be recognised
+    currentTime += 1;
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithButtonState(0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0)))));
+    ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithRelativeMotion(0.f, 0.f))));
+}
+
 TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled,
                   REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
     // Click should still produce button press/release events
@@ -1219,62 +1432,64 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
-    ASSERT_EQ(2u, args.size());
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
 
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithButtonState(0), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
 
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ui::LogicalDisplayId::DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ui::LogicalDisplayId::DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
@@ -1287,1200 +1502,58 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    // We don't need to check args here, since it's covered by the Move test.
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
-// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
-//   logic can be removed.
-class GestureConverterTestWithChoreographer : public GestureConverterTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(true);
-        GestureConverterTestBase::SetUp();
-    }
-};
-
-TEST_F(GestureConverterTestWithChoreographer, Move) {
+TEST_F_WITH_FLAGS(GestureConverterTest, KeypressCancelsHoverMove,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    const nsecs_t gestureStartTime = 1000;
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
+    // Start a move gesture at gestureStartTime
+    Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
 
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
+    // Key presses with IME connection should cancel ongoing move gesture
+    nsecs_t currentTime = gestureStartTime + 100;
+    mFakePolicy->setIsInputMethodConnectionActive(true);
+    mReader->getContext()->setLastKeyDownTimestamp(currentTime);
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT))));
 
-TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(10, 5), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    // Press left and right buttons at once
-    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                        /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
-                        /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    // Then release the left button
-    Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
-                          /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    // Finally release the right button
-    Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
-                           /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, DragWithButton) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    // Press the button
-    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                        /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
-                        /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    // Move
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    // Release the button
-    Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                      /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
-                      /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Scroll) {
-    const nsecs_t downTime = 12345;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -10),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0 - 15),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) {
-    const nsecs_t downTime = 12345;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-10, 0),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(-15, 0),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionClassification(MotionClassification::NONE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
-    // need to use another gesture type, like pinch.
-    Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
-    ASSERT_FALSE(args.empty());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
-                         /*dy=*/0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5,
-                        /*dy=*/10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
-                         /*dy=*/5);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-
-    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
-    // need to use another gesture type, like pinch.
-    Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
-    ASSERT_FALSE(args.empty());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) {
-    // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you
-    // start swiping up and then start moving left or right, it'll return gesture events with only Y
-    // deltas until you lift your fingers and start swiping again. That's why each of these tests
-    // only checks movement in one dimension.
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
-                         /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(4u, args.size());
-
-    // Three fake fingers should be created. We don't actually care where they are, so long as they
-    // move appropriately.
-    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger0Start = arg.pointerCoords[0];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger1Start = arg.pointerCoords[1];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger2Start = arg.pointerCoords[2];
-    args.pop_front();
-
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
-    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
-    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 10);
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 10);
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 10);
-
-    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
-    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
-    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15);
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15);
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
-                         /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(4u, args.size());
-
-    // Three fake fingers should be created. We don't actually care where they are, so long as they
-    // move appropriately.
-    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger0Start = arg.pointerCoords[0];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger1Start = arg.pointerCoords[1];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger2Start = arg.pointerCoords[2];
-    args.pop_front();
-
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10);
-    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10);
-    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10);
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
-
-    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
-    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
-    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                         /* dx= */ 10, /* dy= */ 0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(5u, args.size());
-
-    // Four fake fingers should be created. We don't actually care where they are, so long as they
-    // move appropriately.
-    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger0Start = arg.pointerCoords[0];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger1Start = arg.pointerCoords[1];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger2Start = arg.pointerCoords[2];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    PointerCoords finger3Start = arg.pointerCoords[3];
-    args.pop_front();
-
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10);
-    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10);
-    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10);
-    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 10);
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
-    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
-
-    Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* dx= */ 5, /* dy= */ 0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
-    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
-    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
-    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15);
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
-    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(4u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
-                         GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, 100, 0),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(0.8f, EPSILON), WithPointerCoords(0, -80, 0),
-                      WithPointerCoords(1, 80, 0), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
-                       GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
-                         GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, 100, 0),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.1f, EPSILON), WithPointerCoords(0, -110, 0),
-                      WithPointerCoords(1, 110, 0), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
-                       GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-
-    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                       GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-
-    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                       GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-
-    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
-    // need to use another gesture type, like scroll.
-    Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
-                          /*dy=*/0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture);
-    ASSERT_FALSE(args.empty());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ResetWithButtonPressed) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                        /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
-                        /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-
-    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, -10),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
-                         /*dy=*/10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-
-    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                           /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Tap) {
-    // Tap should produce button press/release events
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                       /* down= */ GESTURES_BUTTON_LEFT,
-                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
-
-    ASSERT_EQ(5u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Click) {
-    // Click should produce button press/release events
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                              /* down= */ GESTURES_BUTTON_LEFT,
-                              /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
-
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* down= */ GESTURES_BUTTON_NONE,
-                            /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
-
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabled,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
-    // Tap should be ignored when disabled
-    mReader->getContext()->setPreventingTouchpadTaps(true);
-
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-
-    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                       /* down= */ GESTURES_BUTTON_LEFT,
-                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
-
-    // no events should be generated
+    // any updates in existing move gesture should be ignored
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
     ASSERT_EQ(0u, args.size());
 
-    // Future taps should be re-enabled
-    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
-}
-
-TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisabled,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
-    // Click should still produce button press/release events
-    mReader->getContext()->setPreventingTouchpadTaps(true);
-
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                              /* down= */ GESTURES_BUTTON_LEFT,
-                              /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* down= */ GESTURES_BUTTON_NONE,
-                            /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
-
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    // Future taps should be re-enabled
-    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
-}
-
-TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, MoveEnablesTapToClick,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
-    // initially disable tap-to-click
-    mReader->getContext()->setPreventingTouchpadTaps(true);
-
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    // Future taps should be re-enabled
-    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+    // New gesture should not be affected
+    currentTime += 100;
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
 }
 
 } // namespace android
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index 85e055d..28699b8 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -18,7 +18,6 @@
 
 #include <NotifyArgsBuilders.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/InputEventBuilders.h>
 #include <linux/input.h>
 
@@ -64,7 +63,7 @@
                                        uint32_t sources = TOUCHSCREEN | STYLUS) {
     auto info = InputDeviceInfo();
     info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id),
-                    "alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+                    "alias", /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID);
     info.addSource(sources);
     return info;
 }
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index c2e67fa..8de28c6 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -16,6 +16,9 @@
 
 #include "../dispatcher/InputDispatcher.h"
 #include "FakeApplicationHandle.h"
+#include "FakeInputDispatcherPolicy.h"
+#include "FakeInputTracingBackend.h"
+#include "FakeWindows.h"
 #include "TestEventMatchers.h"
 
 #include <NotifyArgsBuilders.h>
@@ -32,6 +35,7 @@
 #include <gtest/gtest.h>
 #include <input/BlockingQueue.h>
 #include <input/Input.h>
+#include <input/InputConsumer.h>
 #include <input/PrintTools.h>
 #include <linux/input.h>
 #include <sys/epoll.h>
@@ -55,6 +59,8 @@
 using namespace ftl::flag_operators;
 using testing::AllOf;
 using testing::Not;
+using testing::Pointee;
+using testing::UnorderedElementsAre;
 
 namespace {
 
@@ -66,8 +72,8 @@
 static constexpr int32_t SECOND_DEVICE_ID = 2;
 
 // An arbitrary display id.
-static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
-static constexpr int32_t SECOND_DISPLAY_ID = 1;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId SECOND_DISPLAY_ID = ui::LogicalDisplayId{1};
 
 // Ensure common actions are interchangeable between keys and motions for convenience.
 static_assert(AMOTION_EVENT_ACTION_DOWN == AKEY_EVENT_ACTION_DOWN);
@@ -104,10 +110,6 @@
 static constexpr int32_t POINTER_2_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-// The default pid and uid for windows created on the primary display by the test.
-static constexpr gui::Pid WINDOW_PID{999};
-static constexpr gui::Uid WINDOW_UID{1001};
-
 // The default pid and uid for the windows created on the secondary display by the test.
 static constexpr gui::Pid SECONDARY_WINDOW_PID{1010};
 static constexpr gui::Uid SECONDARY_WINDOW_UID{1012};
@@ -115,24 +117,7 @@
 // An arbitrary pid of the gesture monitor window
 static constexpr gui::Pid MONITOR_PID{2001};
 
-/**
- * If we expect to receive the event, the timeout can be made very long. When the test are running
- * correctly, we will actually never wait until the end of the timeout because the wait will end
- * when the event comes in. Still, this value shouldn't be infinite. During development, a local
- * change may cause the test to fail. This timeout should be short enough to not annoy so that the
- * developer can see the failure quickly (on human scale).
- */
-static constexpr std::chrono::duration CONSUME_TIMEOUT_EVENT_EXPECTED = 1000ms;
-/**
- * When no event is expected, we can have a very short timeout. A large value here would slow down
- * the tests. In the unlikely event of system being too slow, the event may still be present but the
- * timeout would complete before it is consumed. This would result in test flakiness. If this
- * occurs, the flakiness rate would be high. Since the flakes are treated with high priority, this
- * would get noticed and addressed quickly.
- */
-static constexpr std::chrono::duration CONSUME_TIMEOUT_NO_EVENT_EXPECTED = 10ms;
-
-static constexpr int expectedWallpaperFlags =
+static constexpr int EXPECTED_WALLPAPER_FLAGS =
         AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
 using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID;
@@ -143,477 +128,46 @@
 static KeyEvent getTestKeyEvent() {
     KeyEvent event;
 
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0,
-                     ARBITRARY_TIME, ARBITRARY_TIME);
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0,
+                     AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
     return event;
 }
 
-// --- FakeInputDispatcherPolicy ---
-
-class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
-    struct AnrResult {
-        sp<IBinder> token{};
-        gui::Pid pid{gui::Pid::INVALID};
-    };
-
+/**
+ * Provide a local override for a flag value. The value is restored when the object of this class
+ * goes out of scope.
+ * This class is not intended to be used directly, because its usage is cumbersome.
+ * Instead, a wrapper macro SCOPED_FLAG_OVERRIDE is provided.
+ */
+class ScopedFlagOverride {
 public:
-    FakeInputDispatcherPolicy() = default;
-    virtual ~FakeInputDispatcherPolicy() = default;
-
-    void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
-        assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) {
-            ASSERT_EQ(event.getType(), InputEventType::KEY);
-            EXPECT_EQ(event.getDisplayId(), args.displayId);
-
-            const auto& keyEvent = static_cast<const KeyEvent&>(event);
-            EXPECT_EQ(keyEvent.getEventTime(), args.eventTime);
-            EXPECT_EQ(keyEvent.getAction(), args.action);
-        });
+    ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value)
+          : mInitialValue(read()), mWriteValue(write) {
+        mWriteValue(value);
     }
-
-    void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point) {
-        assertFilterInputEventWasCalledInternal([&](const InputEvent& event) {
-            ASSERT_EQ(event.getType(), InputEventType::MOTION);
-            EXPECT_EQ(event.getDisplayId(), args.displayId);
-
-            const auto& motionEvent = static_cast<const MotionEvent&>(event);
-            EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
-            EXPECT_EQ(motionEvent.getAction(), args.action);
-            EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION);
-            EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION);
-            EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION);
-            EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION);
-        });
-    }
-
-    void assertFilterInputEventWasNotCalled() {
-        std::scoped_lock lock(mLock);
-        ASSERT_EQ(nullptr, mFilteredEvent);
-    }
-
-    void assertNotifyConfigurationChangedWasCalled(nsecs_t when) {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mConfigurationChangedTime)
-                << "Timed out waiting for configuration changed call";
-        ASSERT_EQ(*mConfigurationChangedTime, when);
-        mConfigurationChangedTime = std::nullopt;
-    }
-
-    void assertNotifySwitchWasCalled(const NotifySwitchArgs& args) {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mLastNotifySwitch);
-        // We do not check id because it is not exposed to the policy
-        EXPECT_EQ(args.eventTime, mLastNotifySwitch->eventTime);
-        EXPECT_EQ(args.policyFlags, mLastNotifySwitch->policyFlags);
-        EXPECT_EQ(args.switchValues, mLastNotifySwitch->switchValues);
-        EXPECT_EQ(args.switchMask, mLastNotifySwitch->switchMask);
-        mLastNotifySwitch = std::nullopt;
-    }
-
-    void assertOnPointerDownEquals(const sp<IBinder>& touchedToken) {
-        std::scoped_lock lock(mLock);
-        ASSERT_EQ(touchedToken, mOnPointerDownToken);
-        mOnPointerDownToken.clear();
-    }
-
-    void assertOnPointerDownWasNotCalled() {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mOnPointerDownToken == nullptr)
-                << "Expected onPointerDownOutsideFocus to not have been called";
-    }
-
-    // This function must be called soon after the expected ANR timer starts,
-    // because we are also checking how much time has passed.
-    void assertNotifyNoFocusedWindowAnrWasCalled(
-            std::chrono::nanoseconds timeout,
-            const std::shared_ptr<InputApplicationHandle>& expectedApplication) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        std::shared_ptr<InputApplicationHandle> application;
-        ASSERT_NO_FATAL_FAILURE(
-                application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock));
-        ASSERT_EQ(expectedApplication, application);
-    }
-
-    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
-                                                 const sp<WindowInfoHandle>& window) {
-        LOG_ALWAYS_FATAL_IF(window == nullptr, "window should not be null");
-        assertNotifyWindowUnresponsiveWasCalled(timeout, window->getToken(),
-                                                window->getInfo()->ownerPid);
-    }
-
-    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
-                                                 const sp<IBinder>& expectedToken,
-                                                 gui::Pid expectedPid) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        AnrResult result;
-        ASSERT_NO_FATAL_FAILURE(result =
-                                        getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock));
-        ASSERT_EQ(expectedToken, result.token);
-        ASSERT_EQ(expectedPid, result.pid);
-    }
-
-    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
-    sp<IBinder> getUnresponsiveWindowToken(std::chrono::nanoseconds timeout) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        AnrResult result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock);
-        const auto& [token, _] = result;
-        return token;
-    }
-
-    void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken,
-                                               gui::Pid expectedPid) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        AnrResult result;
-        ASSERT_NO_FATAL_FAILURE(
-                result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock));
-        ASSERT_EQ(expectedToken, result.token);
-        ASSERT_EQ(expectedPid, result.pid);
-    }
-
-    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
-    sp<IBinder> getResponsiveWindowToken() {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        AnrResult result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock);
-        const auto& [token, _] = result;
-        return token;
-    }
-
-    void assertNotifyAnrWasNotCalled() {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mAnrApplications.empty());
-        ASSERT_TRUE(mAnrWindows.empty());
-        ASSERT_TRUE(mResponsiveWindows.empty())
-                << "ANR was not called, but please also consume the 'connection is responsive' "
-                   "signal";
-    }
-
-    PointerCaptureRequest assertSetPointerCaptureCalled(bool enabled) {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-
-        if (!mPointerCaptureChangedCondition.wait_for(lock, 100ms,
-                                                      [this, enabled]() REQUIRES(mLock) {
-                                                          return mPointerCaptureRequest->enable ==
-                                                                  enabled;
-                                                      })) {
-            ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << enabled
-                          << ") to be called.";
-            return {};
-        }
-        auto request = *mPointerCaptureRequest;
-        mPointerCaptureRequest.reset();
-        return request;
-    }
-
-    void assertSetPointerCaptureNotCalled() {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-
-        if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) {
-            FAIL() << "Expected setPointerCapture(request) to not be called, but was called. "
-                      "enabled = "
-                   << std::to_string(mPointerCaptureRequest->enable);
-        }
-        mPointerCaptureRequest.reset();
-    }
-
-    void assertDropTargetEquals(const InputDispatcherInterface& dispatcher,
-                                const sp<IBinder>& targetToken) {
-        dispatcher.waitForIdle();
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mNotifyDropWindowWasCalled);
-        ASSERT_EQ(targetToken, mDropTargetWindowToken);
-        mNotifyDropWindowWasCalled = false;
-    }
-
-    void assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token) {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-        std::optional<sp<IBinder>> receivedToken =
-                getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock,
-                                                      mNotifyInputChannelBroken);
-        ASSERT_TRUE(receivedToken.has_value()) << "Did not receive the broken channel token";
-        ASSERT_EQ(token, *receivedToken);
-    }
-
-    /**
-     * Set policy timeout. A value of zero means next key will not be intercepted.
-     */
-    void setInterceptKeyTimeout(std::chrono::milliseconds timeout) {
-        mInterceptKeyTimeout = timeout;
-    }
-
-    void setStaleEventTimeout(std::chrono::nanoseconds timeout) { mStaleEventTimeout = timeout; }
-
-    void assertUserActivityPoked() {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mPokedUserActivity) << "Expected user activity to have been poked";
-    }
-
-    void assertUserActivityNotPoked() {
-        std::scoped_lock lock(mLock);
-        ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked";
-    }
-
-    void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<gui::Uid> uids) {
-        ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms));
-    }
-
-    void assertNotifyDeviceInteractionWasNotCalled() {
-        ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms));
-    }
-
-    void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler) {
-        std::scoped_lock lock(mLock);
-        mUnhandledKeyHandler = handler;
-    }
-
-    void assertUnhandledKeyReported(int32_t keycode) {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-        std::optional<int32_t> unhandledKeycode =
-                getItemFromStorageLockedInterruptible(100ms, mReportedUnhandledKeycodes, lock,
-                                                      mNotifyUnhandledKey);
-        ASSERT_TRUE(unhandledKeycode) << "Expected unhandled key to be reported";
-        ASSERT_EQ(unhandledKeycode, keycode);
-    }
-
-    void assertUnhandledKeyNotReported() {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-        std::optional<int32_t> unhandledKeycode =
-                getItemFromStorageLockedInterruptible(10ms, mReportedUnhandledKeycodes, lock,
-                                                      mNotifyUnhandledKey);
-        ASSERT_FALSE(unhandledKeycode) << "Expected unhandled key NOT to be reported";
-    }
+    ~ScopedFlagOverride() { mWriteValue(mInitialValue); }
 
 private:
-    std::mutex mLock;
-    std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
-    std::optional<nsecs_t> mConfigurationChangedTime GUARDED_BY(mLock);
-    sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
-    std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
-
-    std::condition_variable mPointerCaptureChangedCondition;
-
-    std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock);
-
-    // ANR handling
-    std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
-    std::queue<AnrResult> mAnrWindows GUARDED_BY(mLock);
-    std::queue<AnrResult> mResponsiveWindows GUARDED_BY(mLock);
-    std::condition_variable mNotifyAnr;
-    std::queue<sp<IBinder>> mBrokenInputChannels GUARDED_BY(mLock);
-    std::condition_variable mNotifyInputChannelBroken;
-
-    sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock);
-    bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false;
-    bool mPokedUserActivity GUARDED_BY(mLock) = false;
-
-    std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
-
-    std::chrono::nanoseconds mStaleEventTimeout = 1000ms;
-
-    BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions;
-
-    std::condition_variable mNotifyUnhandledKey;
-    std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock);
-    std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock);
-
-    // All three ANR-related callbacks behave the same way, so we use this generic function to wait
-    // for a specific container to become non-empty. When the container is non-empty, return the
-    // first entry from the container and erase it.
-    template <class T>
-    T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue<T>& storage,
-                                     std::unique_lock<std::mutex>& lock) REQUIRES(mLock) {
-        // If there is an ANR, Dispatcher won't be idle because there are still events
-        // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle
-        // before checking if ANR was called.
-        // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need
-        // to provide it some time to act. 100ms seems reasonable.
-        std::chrono::duration timeToWait = timeout + 100ms; // provide some slack
-        const std::chrono::time_point start = std::chrono::steady_clock::now();
-        std::optional<T> token =
-                getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr);
-        if (!token.has_value()) {
-            ADD_FAILURE() << "Did not receive the ANR callback";
-            return {};
-        }
-
-        const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
-        // Ensure that the ANR didn't get raised too early. We can't be too strict here because
-        // the dispatcher started counting before this function was called
-        if (std::chrono::abs(timeout - waited) > 100ms) {
-            ADD_FAILURE() << "ANR was raised too early or too late. Expected "
-                          << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()
-                          << "ms, but waited "
-                          << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count()
-                          << "ms instead";
-        }
-        return *token;
-    }
-
-    template <class T>
-    std::optional<T> getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout,
-                                                           std::queue<T>& storage,
-                                                           std::unique_lock<std::mutex>& lock,
-                                                           std::condition_variable& condition)
-            REQUIRES(mLock) {
-        condition.wait_for(lock, timeout,
-                           [&storage]() REQUIRES(mLock) { return !storage.empty(); });
-        if (storage.empty()) {
-            return std::nullopt;
-        }
-        T item = storage.front();
-        storage.pop();
-        return std::make_optional(item);
-    }
-
-    void notifyConfigurationChanged(nsecs_t when) override {
-        std::scoped_lock lock(mLock);
-        mConfigurationChangedTime = when;
-    }
-
-    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
-                                  const std::string&) override {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(pid.has_value());
-        mAnrWindows.push({connectionToken, *pid});
-        mNotifyAnr.notify_all();
-    }
-
-    void notifyWindowResponsive(const sp<IBinder>& connectionToken,
-                                std::optional<gui::Pid> pid) override {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(pid.has_value());
-        mResponsiveWindows.push({connectionToken, *pid});
-        mNotifyAnr.notify_all();
-    }
-
-    void notifyNoFocusedWindowAnr(
-            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
-        std::scoped_lock lock(mLock);
-        mAnrApplications.push(applicationHandle);
-        mNotifyAnr.notify_all();
-    }
-
-    void notifyInputChannelBroken(const sp<IBinder>& connectionToken) override {
-        std::scoped_lock lock(mLock);
-        mBrokenInputChannels.push(connectionToken);
-        mNotifyInputChannelBroken.notify_all();
-    }
-
-    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
-
-    void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
-                           InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
-                           const std::vector<float>& values) override {}
-
-    void notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType,
-                              InputDeviceSensorAccuracy accuracy) override {}
-
-    void notifyVibratorState(int32_t deviceId, bool isOn) override {}
-
-    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
-        std::scoped_lock lock(mLock);
-        switch (inputEvent.getType()) {
-            case InputEventType::KEY: {
-                const KeyEvent& keyEvent = static_cast<const KeyEvent&>(inputEvent);
-                mFilteredEvent = std::make_unique<KeyEvent>(keyEvent);
-                break;
-            }
-
-            case InputEventType::MOTION: {
-                const MotionEvent& motionEvent = static_cast<const MotionEvent&>(inputEvent);
-                mFilteredEvent = std::make_unique<MotionEvent>(motionEvent);
-                break;
-            }
-            default: {
-                ADD_FAILURE() << "Should only filter keys or motions";
-                break;
-            }
-        }
-        return true;
-    }
-
-    void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override {
-        if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) {
-            // Clear intercept state when we handled the event.
-            mInterceptKeyTimeout = 0ms;
-        }
-    }
-
-    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
-
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
-        nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
-        // Clear intercept state so we could dispatch the event in next wake.
-        mInterceptKeyTimeout = 0ms;
-        return delay;
-    }
-
-    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event,
-                                                 uint32_t) override {
-        std::scoped_lock lock(mLock);
-        mReportedUnhandledKeycodes.emplace(event.getKeyCode());
-        mNotifyUnhandledKey.notify_all();
-        return mUnhandledKeyHandler != nullptr ? mUnhandledKeyHandler(event) : std::nullopt;
-    }
-
-    void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
-                      uint32_t policyFlags) override {
-        std::scoped_lock lock(mLock);
-        /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
-         * essentially a passthrough for notifySwitch.
-         */
-        mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask);
-    }
-
-    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {
-        std::scoped_lock lock(mLock);
-        mPokedUserActivity = true;
-    }
-
-    bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override {
-        return std::chrono::nanoseconds(currentTime - eventTime) >= mStaleEventTimeout;
-    }
-
-    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {
-        std::scoped_lock lock(mLock);
-        mOnPointerDownToken = newToken;
-    }
-
-    void setPointerCapture(const PointerCaptureRequest& request) override {
-        std::scoped_lock lock(mLock);
-        mPointerCaptureRequest = {request};
-        mPointerCaptureChangedCondition.notify_all();
-    }
-
-    void notifyDropWindow(const sp<IBinder>& token, float x, float y) override {
-        std::scoped_lock lock(mLock);
-        mNotifyDropWindowWasCalled = true;
-        mDropTargetWindowToken = token;
-    }
-
-    void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
-                                 const std::set<gui::Uid>& uids) override {
-        ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids));
-    }
-
-    void assertFilterInputEventWasCalledInternal(
-            const std::function<void(const InputEvent&)>& verify) {
-        std::scoped_lock lock(mLock);
-        ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
-        verify(*mFilteredEvent);
-        mFilteredEvent = nullptr;
-    }
+    const bool mInitialValue;
+    std::function<void(bool)> mWriteValue;
 };
+
+typedef bool (*readFlagValueFunction)();
+typedef void (*writeFlagValueFunction)(bool);
+
+/**
+ * Use this macro to locally override a flag value.
+ * Example usage:
+ *    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+ * Note: this works by creating a local variable in your current scope. Don't call this twice for
+ * the same flag, because the variable names will clash!
+ */
+#define SCOPED_FLAG_OVERRIDE(NAME, VALUE)                                  \
+    readFlagValueFunction read##NAME = com::android::input::flags::NAME;   \
+    writeFlagValueFunction write##NAME = com::android::input::flags::NAME; \
+    ScopedFlagOverride override##NAME(read##NAME, write##NAME, (VALUE))
+
 } // namespace
 
 // --- InputDispatcherTest ---
@@ -622,10 +176,18 @@
 protected:
     std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
     std::unique_ptr<InputDispatcher> mDispatcher;
+    std::shared_ptr<VerifyingTrace> mVerifyingTrace;
 
     void SetUp() override {
+        mVerifyingTrace = std::make_shared<VerifyingTrace>();
+        FakeWindowHandle::sOnEventReceivedCallback = [this](const auto& _1, const auto& _2) {
+            handleEventReceivedByWindow(_1, _2);
+        };
+
         mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
-        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
+        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy,
+                                                        std::make_unique<FakeInputTracingBackend>(
+                                                                mVerifyingTrace));
 
         mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
         // Start InputDispatcher thread
@@ -633,11 +195,35 @@
     }
 
     void TearDown() override {
+        ASSERT_NO_FATAL_FAILURE(mVerifyingTrace->verifyExpectedEventsTraced());
+        FakeWindowHandle::sOnEventReceivedCallback = nullptr;
+
         ASSERT_EQ(OK, mDispatcher->stop());
         mFakePolicy.reset();
         mDispatcher.reset();
     }
 
+    void handleEventReceivedByWindow(const std::unique_ptr<InputEvent>& event,
+                                     const gui::WindowInfo& info) {
+        if (!event) {
+            return;
+        }
+
+        switch (event->getType()) {
+            case InputEventType::KEY: {
+                mVerifyingTrace->expectKeyDispatchTraced(static_cast<KeyEvent&>(*event), info.id);
+                break;
+            }
+            case InputEventType::MOTION: {
+                mVerifyingTrace->expectMotionDispatchTraced(static_cast<MotionEvent&>(*event),
+                                                            info.id);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+
     /**
      * Used for debugging when writing the test
      */
@@ -657,7 +243,7 @@
         request.token = window->getToken();
         request.windowName = window->getName();
         request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
-        request.displayId = window->getInfo()->displayId;
+        request.displayId = window->getInfo()->displayId.val();
         mDispatcher->setFocusedWindow(request);
     }
 };
@@ -666,8 +252,8 @@
     KeyEvent event;
 
     // Rejects undefined key actions.
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC,
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC,
                      /*action=*/-1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME,
                      ARBITRARY_TIME);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
@@ -676,9 +262,9 @@
             << "Should reject key events with undefined action.";
 
     // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0,
-                     ARBITRARY_TIME, ARBITRARY_TIME);
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0,
+                     AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -836,7 +422,8 @@
 }
 
 TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
-    NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1,
+    NotifySwitchArgs args(InputEvent::nextId(), /*eventTime=*/20, /*policyFlags=*/0,
+                          /*switchValues=*/1,
                           /*switchMask=*/2);
     mDispatcher->notifySwitch(args);
 
@@ -848,611 +435,43 @@
 namespace {
 
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 500ms;
-// Default input dispatching timeout if there is no focused application or paused window
-// from which to determine an appropriate dispatching timeout.
-static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds(
-        android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
-        android::base::HwTimeoutMultiplier());
-
-class FakeInputReceiver {
-public:
-    explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name)
-          : mConsumer(std::move(clientChannel)), mName(name) {}
-
-    InputEvent* consume(std::chrono::milliseconds timeout, bool handled = false) {
-        InputEvent* event;
-        std::optional<uint32_t> consumeSeq = receiveEvent(timeout, &event);
-        if (!consumeSeq) {
-            return nullptr;
-        }
-        finishEvent(*consumeSeq, handled);
-        return event;
-    }
-
-    /**
-     * Receive an event without acknowledging it.
-     * Return the sequence number that could later be used to send finished signal.
-     */
-    std::optional<uint32_t> receiveEvent(std::chrono::milliseconds timeout,
-                                         InputEvent** outEvent = nullptr) {
-        uint32_t consumeSeq;
-        InputEvent* event;
-
-        std::chrono::time_point start = std::chrono::steady_clock::now();
-        status_t status = WOULD_BLOCK;
-        while (status == WOULD_BLOCK) {
-            status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
-                                       &event);
-            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
-            if (elapsed > timeout) {
-                break;
-            }
-        }
-
-        if (status == WOULD_BLOCK) {
-            // Just means there's no event available.
-            return std::nullopt;
-        }
-
-        if (status != OK) {
-            ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK.";
-            return std::nullopt;
-        }
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer";
-            return std::nullopt;
-        }
-        if (outEvent != nullptr) {
-            *outEvent = event;
-        }
-        return consumeSeq;
-    }
-
-    /**
-     * To be used together with "receiveEvent" to complete the consumption of an event.
-     */
-    void finishEvent(uint32_t consumeSeq, bool handled = true) {
-        const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled);
-        ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
-    }
-
-    void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
-        const status_t status = mConsumer.sendTimeline(inputEventId, timeline);
-        ASSERT_EQ(OK, status);
-    }
-
-    void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
-                      std::optional<int32_t> expectedDisplayId,
-                      std::optional<int32_t> expectedFlags) {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(expectedEventType, event->getType())
-                << mName.c_str() << " expected " << ftl::enum_string(expectedEventType)
-                << " event, got " << *event;
-
-        if (expectedDisplayId.has_value()) {
-            EXPECT_EQ(expectedDisplayId, event->getDisplayId());
-        }
-
-        switch (expectedEventType) {
-            case InputEventType::KEY: {
-                const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
-                ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
-                if (expectedFlags.has_value()) {
-                    EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
-                }
-                break;
-            }
-            case InputEventType::MOTION: {
-                const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-                ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
-                if (expectedFlags.has_value()) {
-                    EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
-                }
-                break;
-            }
-            case InputEventType::FOCUS: {
-                FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
-            }
-            case InputEventType::CAPTURE: {
-                FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
-            }
-            case InputEventType::TOUCH_MODE: {
-                FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events";
-            }
-            case InputEventType::DRAG: {
-                FAIL() << "Use 'consumeDragEvent' for DRAG events";
-            }
-        }
-    }
-
-    MotionEvent* consumeMotion() {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-
-        if (event == nullptr) {
-            ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one.";
-            return nullptr;
-        }
-
-        if (event->getType() != InputEventType::MOTION) {
-            ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event;
-            return nullptr;
-        }
-        return static_cast<MotionEvent*>(event);
-    }
-
-    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
-        MotionEvent* motionEvent = consumeMotion();
-        ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher;
-        ASSERT_THAT(*motionEvent, matcher);
-    }
-
-    void consumeFocusEvent(bool hasFocus, bool inTouchMode) {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(InputEventType::FOCUS, event->getType())
-                << "Instead of FocusEvent, got " << *event;
-
-        ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
-                << mName.c_str() << ": event displayId should always be NONE.";
-
-        FocusEvent* focusEvent = static_cast<FocusEvent*>(event);
-        EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
-    }
-
-    void consumeCaptureEvent(bool hasCapture) {
-        const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(InputEventType::CAPTURE, event->getType())
-                << "Instead of CaptureEvent, got " << *event;
-
-        ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
-                << mName.c_str() << ": event displayId should always be NONE.";
-
-        const auto& captureEvent = static_cast<const CaptureEvent&>(*event);
-        EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled());
-    }
-
-    void consumeDragEvent(bool isExiting, float x, float y) {
-        const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event;
-
-        EXPECT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
-                << mName.c_str() << ": event displayId should always be NONE.";
-
-        const auto& dragEvent = static_cast<const DragEvent&>(*event);
-        EXPECT_EQ(isExiting, dragEvent.isExiting());
-        EXPECT_EQ(x, dragEvent.getX());
-        EXPECT_EQ(y, dragEvent.getY());
-    }
-
-    void consumeTouchModeEvent(bool inTouchMode) {
-        const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType())
-                << "Instead of TouchModeEvent, got " << *event;
-
-        ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
-                << mName.c_str() << ": event displayId should always be NONE.";
-        const auto& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
-        EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode());
-    }
-
-    void assertNoEvents() {
-        InputEvent* event = consume(CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
-        if (event == nullptr) {
-            return;
-        }
-        if (event->getType() == InputEventType::KEY) {
-            KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
-            ADD_FAILURE() << "Received key event " << keyEvent;
-        } else if (event->getType() == InputEventType::MOTION) {
-            MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
-            ADD_FAILURE() << "Received motion event " << motionEvent;
-        } else if (event->getType() == InputEventType::FOCUS) {
-            FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
-            ADD_FAILURE() << "Received focus event, hasFocus = "
-                          << (focusEvent.getHasFocus() ? "true" : "false");
-        } else if (event->getType() == InputEventType::CAPTURE) {
-            const auto& captureEvent = static_cast<CaptureEvent&>(*event);
-            ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
-                          << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
-        } else if (event->getType() == InputEventType::TOUCH_MODE) {
-            const auto& touchModeEvent = static_cast<TouchModeEvent&>(*event);
-            ADD_FAILURE() << "Received touch mode event, inTouchMode = "
-                          << (touchModeEvent.isInTouchMode() ? "true" : "false");
-        }
-        FAIL() << mName.c_str()
-               << ": should not have received any events, so consume() should return NULL";
-    }
-
-    sp<IBinder> getToken() { return mConsumer.getChannel()->getConnectionToken(); }
-
-    int getChannelFd() { return mConsumer.getChannel()->getFd().get(); }
-
-private:
-    InputConsumer mConsumer;
-    PreallocatedInputEventFactory mEventFactory;
-
-    std::string mName;
-};
-
-class FakeWindowHandle : public WindowInfoHandle {
-public:
-    static const int32_t WIDTH = 600;
-    static const int32_t HEIGHT = 800;
-
-    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-                     const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name,
-                     int32_t displayId, std::optional<sp<IBinder>> token = std::nullopt)
-          : mName(name) {
-        if (token == std::nullopt) {
-            base::Result<std::unique_ptr<InputChannel>> channel =
-                    dispatcher->createInputChannel(name);
-            token = (*channel)->getConnectionToken();
-            mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
-        }
-
-        inputApplicationHandle->updateInfo();
-        mInfo.applicationInfo = *inputApplicationHandle->getInfo();
-
-        mInfo.token = *token;
-        mInfo.id = sId++;
-        mInfo.name = name;
-        mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
-        mInfo.alpha = 1.0;
-        mInfo.frame = Rect(0, 0, WIDTH, HEIGHT);
-        mInfo.transform.set(0, 0);
-        mInfo.globalScaleFactor = 1.0;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
-        mInfo.ownerPid = WINDOW_PID;
-        mInfo.ownerUid = WINDOW_UID;
-        mInfo.displayId = displayId;
-        mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
-    }
-
-    sp<FakeWindowHandle> clone(int32_t displayId) {
-        sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
-        handle->mInfo = mInfo;
-        handle->mInfo.displayId = displayId;
-        handle->mInfo.id = sId++;
-        handle->mInputReceiver = mInputReceiver;
-        return handle;
-    }
-
-    void setTouchable(bool touchable) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
-    }
-
-    void setFocusable(bool focusable) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
-    }
-
-    void setVisible(bool visible) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
-    }
-
-    void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
-        mInfo.dispatchingTimeout = timeout;
-    }
-
-    void setPaused(bool paused) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused);
-    }
-
-    void setPreventSplitting(bool preventSplitting) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting);
-    }
-
-    void setSlippery(bool slippery) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery);
-    }
-
-    void setWatchOutsideTouch(bool watchOutside) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
-    }
-
-    void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
-
-    void setInterceptsStylus(bool interceptsStylus) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
-    }
-
-    void setDropInput(bool dropInput) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
-    }
-
-    void setDropInputIfObscured(bool dropInputIfObscured) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
-    }
-
-    void setNoInputChannel(bool noInputChannel) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
-    }
-
-    void setDisableUserActivity(bool disableUserActivity) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
-    }
-
-    void setAlpha(float alpha) { mInfo.alpha = alpha; }
-
-    void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
-
-    void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
-
-    void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
-        mInfo.frame = frame;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(frame);
-
-        const Rect logicalDisplayFrame = displayTransform.transform(frame);
-        ui::Transform translate;
-        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
-        mInfo.transform = translate * displayTransform;
-    }
-
-    void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
-
-    void setIsWallpaper(bool isWallpaper) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper);
-    }
-
-    void setDupTouchToWallpaper(bool hasWallpaper) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
-    }
-
-    void setTrustedOverlay(bool trustedOverlay) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
-    }
-
-    void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
-        mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
-    }
-
-    void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); }
-
-    void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
-
-    KeyEvent* consumeKey(bool handled = true) {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consume failed : no event";
-            return nullptr;
-        }
-        if (event->getType() != InputEventType::KEY) {
-            ADD_FAILURE() << "Instead of key event, got " << *event;
-            return nullptr;
-        }
-        return static_cast<KeyEvent*>(event);
-    }
-
-    void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
-        KeyEvent* keyEvent = consumeKey();
-        ASSERT_NE(nullptr, keyEvent) << "Did not get a key event, but expected " << matcher;
-        ASSERT_THAT(*keyEvent, matcher);
-    }
-
-    void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags);
-    }
-
-    void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags);
-    }
-
-    void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                             int32_t expectedFlags = 0) {
-        consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(expectedDisplayId),
-                                 WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
-    }
-
-    void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                           int32_t expectedFlags = 0) {
-        consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                                 WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
-    }
-
-    void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                           int32_t expectedFlags = 0) {
-        consumeAnyMotionDown(expectedDisplayId, expectedFlags);
-    }
-
-    void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt,
-                              std::optional<int32_t> expectedFlags = std::nullopt) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
-                     expectedFlags);
-    }
-
-    void consumeMotionPointerDown(int32_t pointerIdx,
-                                  int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                                  int32_t expectedFlags = 0) {
-        int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
-                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags);
-    }
-
-    void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                                int32_t expectedFlags = 0) {
-        int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
-                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags);
-    }
-
-    void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                         int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
-                     expectedFlags);
-    }
-
-    void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                              int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId,
-                     expectedFlags);
-    }
-
-    void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                                              int32_t expectedFlags = 0) {
-        MotionEvent* motionEvent = consumeMotion();
-        ASSERT_NE(nullptr, motionEvent);
-        EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent->getActionMasked());
-        EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getX());
-        EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getY());
-    }
-
-    void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
-        ASSERT_NE(mInputReceiver, nullptr)
-                << "Cannot consume events from a window with no receiver";
-        mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode);
-    }
-
-    void consumeCaptureEvent(bool hasCapture) {
-        ASSERT_NE(mInputReceiver, nullptr)
-                << "Cannot consume events from a window with no receiver";
-        mInputReceiver->consumeCaptureEvent(hasCapture);
-    }
-
-    const MotionEvent& consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
-        MotionEvent* motionEvent = consumeMotion();
-        if (nullptr == motionEvent) {
-            LOG(FATAL) << "Did not get a motion event, but expected " << matcher;
-        }
-        EXPECT_THAT(*motionEvent, matcher);
-        return *motionEvent;
-    }
-
-    void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
-                      std::optional<int32_t> expectedDisplayId,
-                      std::optional<int32_t> expectedFlags) {
-        ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver";
-        mInputReceiver->consumeEvent(expectedEventType, expectedAction, expectedDisplayId,
-                                     expectedFlags);
-    }
-
-    void consumeDragEvent(bool isExiting, float x, float y) {
-        mInputReceiver->consumeDragEvent(isExiting, x, y);
-    }
-
-    void consumeTouchModeEvent(bool inTouchMode) {
-        ASSERT_NE(mInputReceiver, nullptr)
-                << "Cannot consume events from a window with no receiver";
-        mInputReceiver->consumeTouchModeEvent(inTouchMode);
-    }
-
-    std::optional<uint32_t> receiveEvent(InputEvent** outEvent = nullptr) {
-        if (mInputReceiver == nullptr) {
-            ADD_FAILURE() << "Invalid receive event on window with no receiver";
-            return std::nullopt;
-        }
-        return mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED, outEvent);
-    }
-
-    void finishEvent(uint32_t sequenceNum) {
-        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
-        mInputReceiver->finishEvent(sequenceNum);
-    }
-
-    void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
-        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
-        mInputReceiver->sendTimeline(inputEventId, timeline);
-    }
-
-    InputEvent* consume(std::chrono::milliseconds timeout, bool handled = true) {
-        if (mInputReceiver == nullptr) {
-            return nullptr;
-        }
-        return mInputReceiver->consume(timeout, handled);
-    }
-
-    MotionEvent* consumeMotion() {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consume failed : no event";
-            return nullptr;
-        }
-        if (event->getType() != InputEventType::MOTION) {
-            ADD_FAILURE() << "Instead of motion event, got " << *event;
-            return nullptr;
-        }
-        return static_cast<MotionEvent*>(event);
-    }
-
-    void assertNoEvents() {
-        if (mInputReceiver == nullptr &&
-            mInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
-            return; // Can't receive events if the window does not have input channel
-        }
-        ASSERT_NE(nullptr, mInputReceiver)
-                << "Window without InputReceiver must specify feature NO_INPUT_CHANNEL";
-        mInputReceiver->assertNoEvents();
-    }
-
-    sp<IBinder> getToken() { return mInfo.token; }
-
-    const std::string& getName() { return mName; }
-
-    void setOwnerInfo(gui::Pid ownerPid, gui::Uid ownerUid) {
-        mInfo.ownerPid = ownerPid;
-        mInfo.ownerUid = ownerUid;
-    }
-
-    gui::Pid getPid() const { return mInfo.ownerPid; }
-
-    void destroyReceiver() { mInputReceiver = nullptr; }
-
-    int getChannelFd() { return mInputReceiver->getChannelFd(); }
-
-private:
-    FakeWindowHandle(std::string name) : mName(name){};
-    const std::string mName;
-    std::shared_ptr<FakeInputReceiver> mInputReceiver;
-    static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
-    friend class sp<FakeWindowHandle>;
-};
-
-std::atomic<int32_t> FakeWindowHandle::sId{1};
 
 class FakeMonitorReceiver {
 public:
-    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId)
+    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name,
+                        ui::LogicalDisplayId displayId)
           : mInputReceiver(*dispatcher.createInputMonitor(displayId, name, MONITOR_PID), name) {}
 
     sp<IBinder> getToken() { return mInputReceiver.getToken(); }
 
-    void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeKeyDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
                                     expectedFlags);
     }
 
     std::optional<int32_t> receiveEvent() {
-        return mInputReceiver.receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
+        const auto [sequenceNum, _] = mInputReceiver.receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
+        return sequenceNum;
     }
 
     void finishEvent(uint32_t consumeSeq) { return mInputReceiver.finishEvent(consumeSeq); }
 
-    void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeMotionDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
                                     expectedDisplayId, expectedFlags);
     }
 
-    void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeMotionMove(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
                                     expectedDisplayId, expectedFlags);
     }
 
-    void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeMotionUp(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
                                     expectedDisplayId, expectedFlags);
     }
 
-    void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeMotionCancel(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeMotionEvent(
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
                       WithDisplayId(expectedDisplayId),
@@ -1462,7 +481,7 @@
     void consumeMotionPointerDown(int32_t pointerIdx) {
         int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        mInputReceiver.consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
+        mInputReceiver.consumeEvent(InputEventType::MOTION, action, ui::LogicalDisplayId::DEFAULT,
                                     /*expectedFlags=*/0);
     }
 
@@ -1470,9 +489,9 @@
         mInputReceiver.consumeMotionEvent(matcher);
     }
 
-    MotionEvent* consumeMotion() { return mInputReceiver.consumeMotion(); }
+    std::unique_ptr<MotionEvent> consumeMotion() { return mInputReceiver.consumeMotion(); }
 
-    void assertNoEvents() { mInputReceiver.assertNoEvents(); }
+    void assertNoEvents() { mInputReceiver.assertNoEvents(CONSUME_TIMEOUT_NO_EVENT_EXPECTED); }
 
 private:
     FakeInputReceiver mInputReceiver;
@@ -1480,7 +499,7 @@
 
 static InputEventInjectionResult injectKey(
         InputDispatcher& dispatcher, int32_t action, int32_t repeatCount,
-        int32_t displayId = ADISPLAY_ID_NONE,
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID,
         InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
         bool allowKeyRepeat = true, std::optional<gui::Uid> targetUid = {},
@@ -1502,30 +521,34 @@
 
 static void assertInjectedKeyTimesOut(InputDispatcher& dispatcher) {
     InputEventInjectionResult result =
-            injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_NONE,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
+            injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::INVALID, InputEventInjectionSync::WAIT_FOR_RESULT,
+                      CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
     if (result != InputEventInjectionResult::TIMED_OUT) {
         FAIL() << "Injection should have timed out, but got " << ftl::enum_string(result);
     }
 }
 
-static InputEventInjectionResult injectKeyDown(InputDispatcher& dispatcher,
-                                               int32_t displayId = ADISPLAY_ID_NONE) {
+static InputEventInjectionResult injectKeyDown(
+        InputDispatcher& dispatcher,
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId);
 }
 
 // Inject a down event that has key repeat disabled. This allows InputDispatcher to idle without
 // sending a subsequent key up. When key repeat is enabled, the dispatcher cannot idle because it
 // has to be woken up to process the repeating key.
-static InputEventInjectionResult injectKeyDownNoRepeat(InputDispatcher& dispatcher,
-                                                       int32_t displayId = ADISPLAY_ID_NONE) {
+static InputEventInjectionResult injectKeyDownNoRepeat(
+        InputDispatcher& dispatcher,
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId,
                      InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT,
                      /*allowKeyRepeat=*/false);
 }
 
-static InputEventInjectionResult injectKeyUp(InputDispatcher& dispatcher,
-                                             int32_t displayId = ADISPLAY_ID_NONE) {
+static InputEventInjectionResult injectKeyUp(
+        InputDispatcher& dispatcher,
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId);
 }
 
@@ -1539,7 +562,7 @@
 }
 
 static InputEventInjectionResult injectMotionEvent(
-        InputDispatcher& dispatcher, int32_t action, int32_t source, int32_t displayId,
+        InputDispatcher& dispatcher, int32_t action, int32_t source, ui::LogicalDisplayId displayId,
         const PointF& position = {100, 200},
         const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                         AMOTION_EVENT_INVALID_CURSOR_POSITION},
@@ -1565,51 +588,52 @@
 }
 
 static InputEventInjectionResult injectMotionDown(InputDispatcher& dispatcher, int32_t source,
-                                                  int32_t displayId,
+                                                  ui::LogicalDisplayId displayId,
                                                   const PointF& location = {100, 200}) {
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location);
 }
 
 static InputEventInjectionResult injectMotionUp(InputDispatcher& dispatcher, int32_t source,
-                                                int32_t displayId,
+                                                ui::LogicalDisplayId displayId,
                                                 const PointF& location = {100, 200}) {
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location);
 }
 
-static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
+static NotifyKeyArgs generateKeyArgs(
+        int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, POLICY_FLAG_PASS_TO_USER, action, /*flags=*/0, AKEYCODE_A, KEY_A,
-                       AMETA_NONE, currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, POLICY_FLAG_PASS_TO_USER, action,
+                       /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, currentTime);
 
     return args;
 }
 
-static NotifyKeyArgs generateSystemShortcutArgs(int32_t action,
-                                                int32_t displayId = ADISPLAY_ID_NONE) {
+static NotifyKeyArgs generateSystemShortcutArgs(
+        int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C, AMETA_META_ON,
-                       currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C,
+                       AMETA_META_ON, currentTime);
 
     return args;
 }
 
-static NotifyKeyArgs generateAssistantKeyArgs(int32_t action,
-                                              int32_t displayId = ADISPLAY_ID_NONE) {
+static NotifyKeyArgs generateAssistantKeyArgs(
+        int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST, KEY_ASSISTANT,
-                       AMETA_NONE, currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST,
+                       KEY_ASSISTANT, AMETA_NONE, currentTime);
 
     return args;
 }
 
 [[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source,
-                                                         int32_t displayId,
+                                                         ui::LogicalDisplayId displayId,
                                                          const std::vector<PointF>& points) {
     size_t pointerCount = points.size();
     if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_UP) {
@@ -1631,9 +655,9 @@
 
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid motion event.
-    NotifyMotionArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, source, displayId,
-                          POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0, /*flags=*/0,
-                          AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
+    NotifyMotionArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, source,
+                          displayId, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0,
+                          /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
                           pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
@@ -1646,13 +670,15 @@
     return generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, points);
 }
 
-static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId) {
+static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source,
+                                           ui::LogicalDisplayId displayId) {
     return generateMotionArgs(action, source, displayId, {PointF{100, 200}});
 }
 
 static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(
         const PointerCaptureRequest& request) {
-    return NotifyPointerCaptureChangedArgs(/*id=*/0, systemTime(SYSTEM_TIME_MONOTONIC), request);
+    return NotifyPointerCaptureChangedArgs(InputEvent::nextId(), systemTime(SYSTEM_TIME_MONOTONIC),
+                                           request);
 }
 
 } // namespace
@@ -1663,9 +689,9 @@
  */
 TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher,
-                                       "Window that breaks its input channel", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Window that breaks its input channel",
+                                                             ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -1676,31 +702,54 @@
 
 TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+}
+
+using InputDispatcherDeathTest = InputDispatcherTest;
+
+/**
+ * When 'onWindowInfosChanged' arguments contain a duplicate entry for the same window, dispatcher
+ * should crash.
+ */
+TEST_F(InputDispatcherDeathTest, DuplicateWindowInfosAbortDispatcher) {
+    testing::GTEST_FLAG(death_test_style) = "threadsafe";
+    ScopedSilentDeath _silentDeath;
+
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    ASSERT_DEATH(mDispatcher->onWindowInfosChanged(
+                         {{*window->getInfo(), *window->getInfo()}, {}, 0, 0}),
+                 "Incorrect WindowInfosUpdate provided");
 }
 
 TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     // Inject a MotionEvent to an unknown display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_NONE))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::INVALID))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -1710,18 +759,19 @@
  */
 TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -1729,37 +779,40 @@
  */
 TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 // The foreground window should receive the first touch down event.
 TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowTop =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowTop = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                                ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> windowSecond =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged(
             {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Top window should receive the touch down event. Second window should not receive anything.
-    windowTop->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowTop->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     windowSecond->assertNoEvents();
 }
 
@@ -1773,10 +826,12 @@
 TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCanceled) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> foregroundWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                       ui::LogicalDisplayId::DEFAULT);
     foregroundWindow->setDupTouchToWallpaper(true);
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
     mDispatcher->onWindowInfosChanged(
@@ -1790,7 +845,7 @@
 
     // Both foreground window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
@@ -1800,13 +855,13 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     foregroundWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
-    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Now the foreground window goes away, but the wallpaper stays
     mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0});
     foregroundWindow->consumeMotionCancel();
     // Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -1816,8 +871,8 @@
  */
 TEST_F(InputDispatcherTest, CancelAfterPointer0Up) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     // First touch pointer down on right window
@@ -1856,30 +911,32 @@
 TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> foregroundWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                       ui::LogicalDisplayId::DEFAULT);
     foregroundWindow->setDupTouchToWallpaper(true);
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
     mDispatcher->onWindowInfosChanged(
             {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both foreground window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     foregroundWindow->consumeMotionMove();
-    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Wallpaper closes its channel, but the window remains.
     wallpaperWindow->destroyReceiver();
@@ -1891,6 +948,301 @@
     foregroundWindow->consumeMotionCancel();
 }
 
+/**
+ * Two windows: left and right, and a separate wallpaper window underneath each. Device A sends a
+ * down event to the left window. Device B sends a down event to the right window. Next, the right
+ * window disappears. Both the right window and its wallpaper window should receive cancel event.
+ * The left window and its wallpaper window should not receive any events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceDisappearingWindowWithWallpaperWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    // Now right foreground window disappears, but right wallpaper window remains.
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    // Left foreground window and left wallpaper window still exist, and should not receive any
+    // events.
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    // Since right foreground window disappeared, right wallpaper window and right foreground window
+    // should receive cancel events.
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+}
+
+/**
+ * Three windows arranged horizontally and without any overlap. Every window has a
+ * wallpaper window underneath. The middle window also has SLIPPERY flag.
+ * Device A sends a down event to the left window. Device B sends a down event to the middle window.
+ * Next, device B sends move event to the right window. Touch for device B should slip from the
+ * middle window to the right window. Also, the right wallpaper window should receive a down event.
+ * The middle window and its wallpaper window should receive a cancel event. The left window should
+ * not receive any events. If device B continues to report events, the right window and its
+ * wallpaper window should receive remaining events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSlipperyTouchWithWallpaperWindow) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> middleForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    middleForegroundWindow->setDupTouchToWallpaper(true);
+    middleForegroundWindow->setSlippery(true);
+    sp<FakeWindowHandle> middleWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    middleWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightForegroundWindow->setFrame(Rect(200, 0, 300, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *middleForegroundWindow->getInfo(), *middleWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    // Device A sends a DOWN event to the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Device B sends a DOWN event to the middle window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Move the events of device B to the top of the right window.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Make sure the window on the right can receive the remaining events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(251).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    middleForegroundWindow->assertNoEvents();
+    middleWallpaperWindow->assertNoEvents();
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+}
+
+/**
+ * Similar to the test above, we have three windows, they are arranged horizontally and without any
+ * overlap, and every window has a wallpaper window. The middle window is a simple window, without
+ * any special flags. Device A reports a down event that lands in left window. Device B sends a down
+ * event to the middle window and then touch is transferred from the middle window to the right
+ * window. The right window and its wallpaper window should receive a down event. The middle window
+ * and its wallpaper window should receive a cancel event. The left window should not receive any
+ * events. Subsequent events reported by device B should go to the right window and its wallpaper.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceTouchTransferWithWallpaperWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> middleForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    middleForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> middleWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    middleWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightForegroundWindow->setFrame(Rect(200, 0, 300, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *middleForegroundWindow->getInfo(), *middleWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    // Device A touch down on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Device B touch down on the middle window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    // Transfer touch from the middle window to the right window.
+    ASSERT_TRUE(mDispatcher->transferTouchGesture(middleForegroundWindow->getToken(),
+                                                  rightForegroundWindow->getToken()));
+
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+    rightForegroundWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+
+    // Make sure the right window can receive the remaining events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(251).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    middleForegroundWindow->assertNoEvents();
+    middleWallpaperWindow->assertNoEvents();
+    rightForegroundWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+}
+
 class ShouldSplitTouchFixture : public InputDispatcherTest,
                                 public ::testing::WithParamInterface<bool> {};
 INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
@@ -1903,12 +1255,14 @@
 TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> foregroundWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                       ui::LogicalDisplayId::DEFAULT);
     foregroundWindow->setDupTouchToWallpaper(true);
     foregroundWindow->setPreventSplitting(GetParam());
 
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
     mDispatcher->onWindowInfosChanged(
@@ -1916,13 +1270,13 @@
 
     // Touch down on top window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 100}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both top window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Second finger down on the top window
     const MotionEvent secondFingerDownEvent =
@@ -1935,14 +1289,13 @@
               injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
     foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
-    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
-                                              expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ui::LogicalDisplayId::DEFAULT,
+                                              EXPECTED_WALLPAPER_FLAGS);
 
     const MotionEvent secondFingerUpEvent =
             MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
@@ -1952,13 +1305,14 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     foregroundWindow->consumeMotionPointerUp(0);
-    wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionPointerUp(0, ui::LogicalDisplayId::DEFAULT,
+                                            EXPECTED_WALLPAPER_FLAGS);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
-                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .displayId(ui::LogicalDisplayId::DEFAULT)
                                         .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                                         .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
                                                          .x(100)
@@ -1966,8 +1320,8 @@
                                         .build(),
                                 INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    foregroundWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
+    wallpaperWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -1980,18 +1334,19 @@
  */
 TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
     leftWindow->setDupTouchToWallpaper(true);
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
     rightWindow->setDupTouchToWallpaper(true);
 
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
     wallpaperWindow->setIsWallpaper(true);
 
@@ -2003,13 +1358,13 @@
 
     // Touch down on left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 100}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both foreground window and its wallpaper should receive the touch down
     leftWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Second finger down on the right window
     const MotionEvent secondFingerDownEvent =
@@ -2025,16 +1380,16 @@
 
     leftWindow->consumeMotionMove();
     // Since the touch is split, right window gets ACTION_DOWN
-    rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
-                                              expectedWallpaperFlags);
+    rightWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ui::LogicalDisplayId::DEFAULT,
+                                              EXPECTED_WALLPAPER_FLAGS);
 
     // Now, leftWindow, which received the first finger, disappears.
     mDispatcher->onWindowInfosChanged(
             {{*rightWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     leftWindow->consumeMotionCancel();
     // Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // The pointer that's still down on the right window moves, and goes to the right window only.
     // As far as the dispatcher's concerned though, both pointers are still present.
@@ -2061,18 +1416,19 @@
  */
 TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
     leftWindow->setDupTouchToWallpaper(true);
     leftWindow->setSlippery(true);
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
     mDispatcher->onWindowInfosChanged(
@@ -2083,23 +1439,23 @@
 
     // Touch down on left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 100}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both foreground window and its wallpaper should receive the touch down
     leftWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Move to right window, the left window should receive cancel.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {201, 100}))
+                                ui::LogicalDisplayId::DEFAULT, {201, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     leftWindow->consumeMotionCancel();
-    rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    rightWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -2120,14 +1476,14 @@
  */
 TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 200, 200));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
@@ -2198,8 +1554,8 @@
 TEST_F(InputDispatcherTest, HoverEventInconsistentPolicy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 300, 300));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2256,14 +1612,16 @@
  * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
  * In the buggy implementation, a tap on the right window would cause a crash.
  */
-TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) {
+TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -2352,6 +1710,99 @@
 }
 
 /**
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered from the right window into the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ * The second tap is done onto the right window.
+ * The mouse and tap are from two different devices.
+ * We technically don't need to set the downtime / eventtime for these events, but setting these
+ * explicitly helps during debugging.
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, a tap on the right window would cause a crash.
+ */
+TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+    // All times need to start at the current time, otherwise the dispatcher will drop the events as
+    // stale.
+    const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    const int32_t mouseDeviceId = 6;
+    const int32_t touchDeviceId = 4;
+    // Move the cursor from right
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .downTime(baseTime + 10)
+                    .eventTime(baseTime + 20)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(100))
+                    .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // .. to the left window
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .downTime(baseTime + 10)
+                    .eventTime(baseTime + 30)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(100))
+                    .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+    // Now tap the left window
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .downTime(baseTime + 40)
+                    .eventTime(baseTime + 40)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                    .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // release tap
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .downTime(baseTime + 40)
+                                      .eventTime(baseTime + 50)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    // Tap the window on the right
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .downTime(baseTime + 60)
+                    .eventTime(baseTime + 60)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                    .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // release tap
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .downTime(baseTime + 60)
+                                      .eventTime(baseTime + 70)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    // No more events
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * Start hovering in a window. While this hover is still active, make another window appear on top.
  * The top, obstructing window has no input channel, so it's not supposed to receive input.
  * While the top window is present, the hovering is stopped.
@@ -2362,8 +1813,8 @@
  */
 TEST_F(InputDispatcherTest, HoverWhileWindowAppears) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     // Only a single window is present at first
@@ -2378,8 +1829,8 @@
     // Now, an obscuring window appears!
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
-                                       ADISPLAY_ID_DEFAULT,
-                                       /*token=*/std::make_optional<sp<IBinder>>(nullptr));
+                                       ui::LogicalDisplayId::DEFAULT,
+                                       /*createInputChannel=*/false);
     obscuringWindow->setFrame(Rect(0, 0, 200, 200));
     obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
     obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID);
@@ -2411,8 +1862,8 @@
  */
 TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     // Only a single window is present at first
@@ -2427,8 +1878,8 @@
     // Now, an obscuring window appears!
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
-                                       ADISPLAY_ID_DEFAULT,
-                                       /*token=*/std::make_optional<sp<IBinder>>(nullptr));
+                                       ui::LogicalDisplayId::DEFAULT,
+                                       /*createInputChannel=*/false);
     obscuringWindow->setFrame(Rect(0, 0, 200, 200));
     obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
     obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID);
@@ -2469,8 +1920,8 @@
  */
 TEST_F(InputDispatcherTest, HoverMoveAndScroll) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -2499,9 +1950,10 @@
  * touch is dropped, because stylus should be preferred over touch.
  */
 TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2541,16 +1993,65 @@
 }
 
 /**
+ * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because multiple devices are allowed to be active in the same window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownDoesNotBlockTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Touch move
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus move
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+
+    window->assertNoEvents();
+}
+
+/**
  * One window and one spy window. Stylus down on the window. Next, touch from another device goes
  * down. Ensure that touch is dropped, because stylus should be preferred over touch.
  * Similar test as above, but with added SPY window.
  */
 TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 200, 200));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
@@ -2599,13 +2100,77 @@
 }
 
 /**
+ * One window and one spy window. Stylus down on the window. Next, touch from another device goes
+ * down. Ensure that touch is not dropped, because multiple devices can be active at the same time.
+ * Similar test as above, but with added SPY window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyDoesNotBlockTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Touch move
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                        WithCoords(101, 111)));
+
+    window->assertNoEvents();
+    spyWindow->assertNoEvents();
+}
+
+/**
  * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
  * touch is dropped, because stylus hover takes precedence.
  */
 TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2650,13 +2215,68 @@
 }
 
 /**
+ * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because stylus hover and touch can be both active at the same time.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    // Touch move on window
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
+
+    // and subsequent touches continue to work
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    window->assertNoEvents();
+}
+
+/**
  * One window. Touch down on the window. Then, stylus hover on the window from another device.
  * Ensure that touch is canceled, because stylus hover should take precedence.
  */
 TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2703,14 +2323,69 @@
 }
 
 /**
+ * One window. Touch down on the window. Then, stylus hover on the window from another device.
+ * Ensure that touch is not canceled, because stylus hover can be active at the same time as touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus hover on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    // Stylus hover movement is received normally
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
+
+    // Subsequent touch movements also work
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(142, 147)));
+
+    window->assertNoEvents();
+}
+
+/**
  * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
  * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should
  * become active.
  */
 TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2755,13 +2430,62 @@
 }
 
 /**
+ * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
+ * both stylus devices can function simultaneously.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TwoStylusDevicesActiveAtTheSameTime) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t stylusDeviceId1 = 3;
+    constexpr int32_t stylusDeviceId2 = 5;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+
+    // Second stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+    window->assertNoEvents();
+}
+
+/**
  * One window. Touch down on the window. Then, stylus down on the window from another device.
  * Ensure that is canceled, because stylus down should be preferred over touch.
  */
 TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2799,20 +2523,72 @@
 }
 
 /**
+ * One window. Touch down on the window. Then, stylus down on the window from another device.
+ * Ensure that both touch and stylus are functioning independently.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+
+    // Touch continues to work too
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(148).y(149))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * Two windows: a window on the left and a window on the right.
  * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
  * down. Then, on the left window, also place second touch pointer down.
  * This test tries to reproduce a crash.
  * In the buggy implementation, second pointer down on the left window would cause a crash.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -2884,16 +2660,98 @@
 
 /**
  * Two windows: a window on the left and a window on the right.
+ * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
+ * down. Then, on the left window, also place second touch pointer down.
+ * This test tries to reproduce a crash.
+ * In the buggy implementation, second pointer down on the left window would cause a crash.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+
+    // Start hovering over the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+    // Mouse down on left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
+
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                    .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    // First touch pointer down on right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    leftWindow->assertNoEvents();
+
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Second touch pointer down on left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    // Since this is now a new splittable pointer going down on the left window, and it's coming
+    // from a different device, it will be split and delivered to left window separately.
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    // This MOVE event is not necessary (doesn't carry any new information), but it's there in the
+    // current implementation.
+    const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}};
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers)));
+
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
+ * Two windows: a window on the left and a window on the right.
  * Mouse is hovered on the left window and stylus is hovered on the right window.
  */
 TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -2943,21 +2801,22 @@
  * Stylus down on the left window and remains down. Touch goes down on the right and remains down.
  * Check the stream that's received by the spy.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 400, 400));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
 
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
 
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
@@ -3013,6 +2872,83 @@
 }
 
 /**
+ * Three windows: a window on the left and a window on the right.
+ * And a spy window that's positioned above all of them.
+ * Stylus down on the left window and remains down. Touch goes down on the right and remains down.
+ * Check the stream that's received by the spy.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 400, 400));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 1;
+    const int32_t touchDeviceId = 2;
+
+    // Stylus down on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Stylus movements continue. They should be delivered to the left window and to the spy window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+
+    // Further touch MOVE events keep going to the right window and to the spy
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110))
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    spyWindow->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * Three windows: a window on the left, a window on the right, and a spy window positioned above
  * both.
  * Check hover in left window and touch down in the right window.
@@ -3021,20 +2957,21 @@
  * respectively.
  */
 TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 400, 400));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
 
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -3088,6 +3025,84 @@
 }
 
 /**
+ * Three windows: a window on the left, a window on the right, and a spy window positioned above
+ * both.
+ * Check hover in left window and touch down in the right window.
+ * At first, spy should receive hover. Next, spy should receive touch.
+ * At the same time, left and right should be getting independent streams of hovering and touch,
+ * respectively.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverDoesNotBlockTouchWithSpy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 400, 400));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 1;
+    const int32_t touchDeviceId = 2;
+
+    // Stylus hover on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on the right window.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    leftWindow->assertNoEvents();
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Stylus movements continue. They should be delivered to the left window and the spy.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+    // Touch movements continue. They should be delivered to the right window and the spy
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101))
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    spyWindow->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * On a single window, use two different devices: mouse and touch.
  * Touch happens first, with two pointers going down, and then the first pointer leaving.
  * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL.
@@ -3095,10 +3110,11 @@
  * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not
  * represent a new gesture.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) {
+TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3168,13 +3184,92 @@
 }
 
 /**
+ * On a single window, use two different devices: mouse and touch.
+ * Touch happens first, with two pointers going down, and then the first pointer leaving.
+ * Mouse is clicked next, which should not interfere with the touch stream.
+ * Finally, a second touch pointer goes down again. Ensure the second touch pointer is also
+ * delivered correctly.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+
+    // First touch pointer down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    // Second touch pointer down
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+                                      .build());
+    // First touch pointer lifts. The second one remains down
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_0_UP));
+
+    // Mouse down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100))
+                    .build());
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    // Second touch pointer down.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_0_DOWN), WithDeviceId(touchDeviceId),
+                                     WithPointerCount(2u)));
+
+    // Mouse movements should continue to work
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(330).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+
+    window->assertNoEvents();
+}
+
+/**
  * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels
  * the injected event.
  */
-TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) {
+TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3204,6 +3299,40 @@
 }
 
 /**
+ * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event runs
+ * parallel to the injected event.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t touchDeviceId = 4;
+    // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after
+    // completion.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                        .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+                                        .build()));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID)));
+
+    // Now a real touch comes. The injected pointer will remain, and the new gesture will also be
+    // allowed through.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * This test is similar to the test above, but the sequence of injected events is different.
  *
  * Two windows: a window on the left and a window on the right.
@@ -3217,14 +3346,15 @@
  * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
  * In the buggy implementation, second finger down on the left window would cause a crash.
  */
-TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
+TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -3289,14 +3419,91 @@
 }
 
 /**
+ * This test is similar to the test above, but the sequence of injected events is different.
+ *
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered over the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ *
+ * After that, we send one finger down onto the right window, and then a second finger down onto
+ * the left window.
+ * The touch is split, so this last gesture should cause 2 ACTION_DOWN events, one in the right
+ * window (first), and then another on the left window (second).
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, second finger down on the left window would cause a crash.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t mouseDeviceId = 6;
+    const int32_t touchDeviceId = 4;
+    // Hover over the left window. Keep the cursor there.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+                    .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // Tap on left window
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                    .build());
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithDeviceId(touchDeviceId)));
+
+    // First finger down on right window
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                    .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // Second finger down on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+
+    // No more events
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs.
  * While the touch is down, new hover events from the stylus device should be ignored. After the
  * touch is gone, stylus hovering should start working again.
  */
 TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3355,19 +3562,220 @@
 }
 
 /**
+ * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs.
+ * While the touch is down, hovering from the stylus is not affected. After the touch is gone,
+ * check that the stylus hovering continues to work.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverWithTouchTap) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+    // Start hovering with stylus
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Finger down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Continue hovering with stylus.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
+                                      .build());
+    // Hovers continue to work
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+    // Lift up the finger
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    window->assertNoEvents();
+}
+
+/**
+ * If stylus is down anywhere on the screen, then touches should not be delivered to windows that
+ * have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH.
+ *
+ * Two windows: one on the left and one on the right.
+ * The window on the right has GLOBAL_STYLUS_BLOCKS_TOUCH config.
+ * Stylus down on the left window, and then touch down on the right window.
+ * Check that the right window doesn't get touches while the stylus is down on the left window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusDownBlocksTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> sbtRightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher,
+                                       "Stylus blocks touch (right) window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    sbtRightWindow->setFrame(Rect(100, 100, 200, 200));
+    sbtRightWindow->setGlobalStylusBlocksTouch(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *sbtRightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+
+    // Stylus down in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(52))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Finger tap on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+
+    // The touch should be blocked, because stylus is down somewhere else on screen!
+    sbtRightWindow->assertNoEvents();
+
+    // Continue stylus motion, and ensure it's not impacted.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithDeviceId(stylusDeviceId)));
+
+    // Now that the stylus gesture is done, touches should be getting delivered correctly.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(153))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    sbtRightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
+ * If stylus is hovering anywhere on the screen, then touches should not be delivered to windows
+ * that have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH.
+ *
+ * Two windows: one on the left and one on the right.
+ * The window on the right has GLOBAL_STYLUS_BLOCKS_TOUCH config.
+ * Stylus hover on the left window, and then touch down on the right window.
+ * Check that the right window doesn't get touches while the stylus is hovering on the left window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusHoverBlocksTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> sbtRightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher,
+                                       "Stylus blocks touch (right) window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    sbtRightWindow->setFrame(Rect(100, 100, 200, 200));
+    sbtRightWindow->setGlobalStylusBlocksTouch(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *sbtRightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+
+    // Stylus hover in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(52))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Finger tap on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+
+    // The touch should be blocked, because stylus is hovering somewhere else on screen!
+    sbtRightWindow->assertNoEvents();
+
+    // Continue stylus motion, and ensure it's not impacted.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+
+    // Now that the stylus gesture is done, touches should be getting delivered correctly.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(153))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    sbtRightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * A spy window above a window with no input channel.
  * Start hovering with a stylus device, and then tap with it.
  * Ensure spy window receives the entire sequence.
  */
 TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 200, 200));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setNoInputChannel(true);
     window->setFrame(Rect(0, 0, 200, 200));
 
@@ -3420,8 +3828,8 @@
 TEST_F(InputDispatcherTest, StaleStylusHoverGestureIsComplete) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3456,15 +3864,16 @@
  * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active.
  * While the mouse is down, new move events from the touch device should be ignored.
  */
-TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) {
+TEST_F(InputDispatcherTest, TouchPilferAndMouseMove_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 200, 200));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
@@ -3553,6 +3962,114 @@
 }
 
 /**
+ * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream.
+ * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse
+ * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active.
+ * While the mouse is down, new move events from the touch device should continue to work.
+ */
+TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    const int32_t mouseDeviceId = 7;
+    const int32_t touchDeviceId = 4;
+
+    // Hover a bit with mouse first
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+    // Start touching
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55))
+                                      .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    // Pilfer the stream
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+    // Hover is not pilfered! Only touch.
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60))
+                                      .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    // Mouse down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
+
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                    .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    // Mouse move!
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+                                      .build());
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+
+    // Touch move!
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65))
+                                      .build());
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // No more events
+    spyWindow->assertNoEvents();
+    window->assertNoEvents();
+}
+
+/**
  * On the display, have a single window, and also an area where there's no window.
  * First pointer touches the "no window" area of the screen. Second pointer touches the window.
  * Make sure that the window receives the second pointer, and first pointer is simply ignored.
@@ -3622,60 +4139,69 @@
 
     // Touch down on the first window
     mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}));
-
     mDispatcher->waitForIdle();
 
-    MotionEvent* motionEvent1 = window1->consumeMotion();
-    ASSERT_NE(motionEvent1, nullptr);
+    const std::unique_ptr<MotionEvent> firstDown =
+            window1->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+    ASSERT_EQ(firstDown->getDownTime(), firstDown->getEventTime());
     window2->assertNoEvents();
-    nsecs_t downTimeForWindow1 = motionEvent1->getDownTime();
-    ASSERT_EQ(motionEvent1->getDownTime(), motionEvent1->getEventTime());
 
     // Now touch down on the window with another pointer
     mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}));
     mDispatcher->waitForIdle();
-    MotionEvent* motionEvent2 = window2->consumeMotion();
-    ASSERT_NE(motionEvent2, nullptr);
-    nsecs_t downTimeForWindow2 = motionEvent2->getDownTime();
-    ASSERT_NE(downTimeForWindow1, downTimeForWindow2);
-    ASSERT_EQ(motionEvent2->getDownTime(), motionEvent2->getEventTime());
+
+    const std::unique_ptr<MotionEvent> secondDown =
+            window2->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+    ASSERT_EQ(secondDown->getDownTime(), secondDown->getEventTime());
+    ASSERT_NE(firstDown->getDownTime(), secondDown->getDownTime());
+    // We currently send MOVE events to all windows receiving a split touch when there is any change
+    // in the touch state, even when none of the pointers in the split window actually moved.
+    // Document this behavior in the test.
+    window1->consumeMotionMove();
 
     // Now move the pointer on the second window
     mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}}));
     mDispatcher->waitForIdle();
-    window2->consumeMotionEvent(WithDownTime(downTimeForWindow2));
+
+    window2->consumeMotionEvent(WithDownTime(secondDown->getDownTime()));
+    window1->consumeMotionEvent(WithDownTime(firstDown->getDownTime()));
 
     // Now add new touch down on the second window
     mDispatcher->notifyMotion(generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}}));
     mDispatcher->waitForIdle();
-    window2->consumeMotionEvent(WithDownTime(downTimeForWindow2));
 
-    // TODO(b/232530217): do not send the unnecessary MOVE event and delete the next line
-    window1->consumeMotionMove();
-    window1->assertNoEvents();
+    window2->consumeMotionEvent(
+            AllOf(WithMotionAction(POINTER_1_DOWN), WithDownTime(secondDown->getDownTime())));
+    window1->consumeMotionEvent(WithDownTime(firstDown->getDownTime()));
 
     // Now move the pointer on the first window
     mDispatcher->notifyMotion(
             generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}}));
     mDispatcher->waitForIdle();
-    window1->consumeMotionEvent(WithDownTime(downTimeForWindow1));
 
+    window1->consumeMotionEvent(WithDownTime(firstDown->getDownTime()));
+    window2->consumeMotionEvent(WithDownTime(secondDown->getDownTime()));
+
+    // Now add new touch down on the first window
     mDispatcher->notifyMotion(
             generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}}));
     mDispatcher->waitForIdle();
-    window1->consumeMotionEvent(WithDownTime(downTimeForWindow1));
+
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(POINTER_1_DOWN), WithDownTime(firstDown->getDownTime())));
+    window2->consumeMotionEvent(WithDownTime(secondDown->getDownTime()));
 }
 
 TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowLeft =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowLeft = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     windowLeft->setFrame(Rect(0, 0, 600, 800));
-    sp<FakeWindowHandle> windowRight =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowRight = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     windowRight->setFrame(Rect(600, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged(
             {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
@@ -3735,7 +4261,7 @@
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
-    windowLeft->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    windowLeft->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
     // Move mouse cursor back to right window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3756,10 +4282,11 @@
  * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the
  * currently active gesture should be canceled, and the new one should proceed.
  */
-TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) {
+TEST_F(InputDispatcherTest, TwoPointersDownMouseClick_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 600, 800));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3810,19 +4337,78 @@
     window->assertNoEvents();
 }
 
+/**
+ * Put two fingers down (and don't release them) and click the mouse button.
+ * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the
+ * currently active gesture should not be canceled, and the new one should proceed in parallel.
+ */
+TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 600, 800));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+
+    // Two pointers down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+    // Send a series of mouse events for a mouse click
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
+                    .build());
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    // Try to send more touch events while the mouse is down. Since it's a continuation of an
+    // already active gesture, it should be sent normally.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    window->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, HoverWithSpyWindows) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 600, 800));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 600, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Send mouse cursor to the window
@@ -3842,19 +4428,20 @@
     spyWindow->assertNoEvents();
 }
 
-TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 600, 800));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 600, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Send mouse cursor to the window
@@ -3948,15 +4535,111 @@
     spyWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 600, 800));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 600, 800));
+
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Send mouse cursor to the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
+
+    // Move mouse cursor
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+                                      .build());
+
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+    // Touch down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(SECOND_DEVICE_ID)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // pilfer the motion, retaining the gesture on the spy window.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    // Mouse hover is not pilfered
+
+    // Touch UP on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(SECOND_DEVICE_ID)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+                                      .build());
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going
+    // to send a new gesture. It should again go to both windows (spy and the window below), just
+    // like the first gesture did, before pilfering. The window configuration has not changed.
+
+    // One more tap - DOWN
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(SECOND_DEVICE_ID)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // Touch UP on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(SECOND_DEVICE_ID)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // Mouse movement continues normally as well
+    // Move mouse cursor
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(120).y(130))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+
+    window->assertNoEvents();
+    spyWindow->assertNoEvents();
+}
+
 // This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected
 // directly in this test.
 TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4003,7 +4686,7 @@
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
-    window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
     // We already canceled the hovering implicitly by injecting the "DOWN" event without lifting the
     // hover first. Therefore, injection of HOVER_EXIT is inconsistent, and should fail.
@@ -4022,11 +4705,11 @@
  */
 TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4050,11 +4733,11 @@
                   REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags,
                                                        a11y_crash_on_inconsistent_event_stream))) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4073,10 +4756,11 @@
 /**
  * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT.
  */
-TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
+TEST_F(InputDispatcherTest, TouchDownAfterMouseHover_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4104,14 +4788,46 @@
 }
 
 /**
+ * If mouse is hovering when the touch goes down, the hovering should not be stopped.
+ */
+TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t mouseDeviceId = 7;
+    const int32_t touchDeviceId = 4;
+
+    // Start hovering with the mouse
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+    // Touch goes down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * Inject a mouse hover event followed by a tap from touchscreen.
  * The tap causes a HOVER_EXIT event to be generated because the current event
  * stream's source has been switched.
  */
-TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4142,11 +4858,50 @@
                                              WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
 }
 
+/**
+ * Send a mouse hover event followed by a tap from touchscreen.
+ * The tap causes a HOVER_EXIT event to be generated because the current event
+ * stream's source has been switched.
+ */
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                     WithSource(AINPUT_SOURCE_MOUSE)));
+
+    // Tap on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                     WithSource(AINPUT_SOURCE_MOUSE)));
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                     WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                     WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+}
+
 TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> windowDefaultDisplay =
             sp<FakeWindowHandle>::make(application, mDispatcher, "DefaultDisplay",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     windowDefaultDisplay->setFrame(Rect(0, 0, 600, 800));
     sp<FakeWindowHandle> windowSecondDisplay =
             sp<FakeWindowHandle>::make(application, mDispatcher, "SecondDisplay",
@@ -4162,7 +4917,7 @@
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .displayId(ui::LogicalDisplayId::DEFAULT)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(600))
                                         .build()));
     windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
@@ -4181,7 +4936,7 @@
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .displayId(ui::LogicalDisplayId::DEFAULT)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(400).y(700))
                                         .build()));
     windowDefaultDisplay->consumeMotionEvent(
@@ -4193,14 +4948,14 @@
 TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> windowLeft =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowLeft = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     windowLeft->setFrame(Rect(0, 0, 600, 800));
-    sp<FakeWindowHandle> windowRight =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowRight = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     windowRight->setFrame(Rect(600, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged(
             {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
@@ -4209,15 +4964,16 @@
     // left window. This event should be dispatched to the left window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE,
-                                ADISPLAY_ID_DEFAULT, {610, 400}, {599, 400}));
-    windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+                                ui::LogicalDisplayId::DEFAULT, {610, 400}, {599, 400}));
+    windowLeft->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     windowRight->assertNoEvents();
 }
 
 TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4225,42 +4981,44 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive key down event.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // When device reset happens, that key stream should be terminated with FLAG_CANCELED
     // on the app side.
     mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
-    window->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
-                         AKEY_EVENT_FLAG_CANCELED);
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT, AKEY_EVENT_FLAG_CANCELED);
 }
 
 TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive motion down event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // When device reset happens, that motion stream should be terminated with ACTION_CANCEL
     // on the app side.
     mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
     window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
 }
 
 TEST_F(InputDispatcherTest, NotifyDeviceResetCancelsHoveringStream) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4283,8 +5041,9 @@
 
 TEST_F(InputDispatcherTest, InterceptKeyByPolicy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4292,13 +5051,14 @@
 
     window->consumeFocusEvent(true);
 
-    const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    const NotifyKeyArgs keyArgs =
+            generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT);
     const std::chrono::milliseconds interceptKeyTimeout = 50ms;
     const nsecs_t injectTime = keyArgs.eventTime;
     mFakePolicy->setInterceptKeyTimeout(interceptKeyTimeout);
     mDispatcher->notifyKey(keyArgs);
     // The dispatching time should be always greater than or equal to intercept key timeout.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     ASSERT_TRUE((systemTime(SYSTEM_TIME_MONOTONIC) - injectTime) >=
                 std::chrono::nanoseconds(interceptKeyTimeout).count());
 }
@@ -4308,8 +5068,9 @@
  */
 TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4317,15 +5078,15 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // Set a value that's significantly larger than the default consumption timeout. If the
     // implementation is correct, the actual value doesn't matter; it won't slow down the test.
     mFakePolicy->setInterceptKeyTimeout(600ms);
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT));
     // Window should receive key event immediately when same key up.
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -4337,13 +5098,13 @@
  */
 TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect{0, 0, 100, 100});
 
     sp<FakeWindowHandle> outsideWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Outside Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     outsideWindow->setFrame(Rect{100, 100, 200, 200});
     outsideWindow->setWatchOutsideTouch(true);
     // outsideWindow must be above 'window' to receive ACTION_OUTSIDE events when 'window' is tapped
@@ -4351,8 +5112,8 @@
 
     // Tap on first window.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {PointF{50, 50}}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{50, 50}}));
     window->consumeMotionDown();
     // The coordinates of the tap in 'outsideWindow' are relative to its top left corner.
     // Therefore, we should offset them by (100, 100) relative to the screen's top left corner.
@@ -4361,12 +5122,102 @@
 
     // Ensure outsideWindow doesn't get any more events for the gesture.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{51, 51}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{51, 51}}));
     window->consumeMotionMove();
     outsideWindow->assertNoEvents();
 }
 
 /**
+ * Three windows:
+ * - Left window
+ * - Right window
+ * - Outside window(watch for ACTION_OUTSIDE events)
+ * The windows "left" and "outside" share the same owner, the window "right" has a different owner,
+ * In order to allow the outside window can receive the ACTION_OUTSIDE events, the outside window is
+ * positioned above the "left" and "right" windows, and it doesn't overlap with them.
+ *
+ * First, device A report a down event landed in the right window, the outside window can receive
+ * an ACTION_OUTSIDE event that with zeroed coordinates, the device B report a down event landed
+ * in the left window, the outside window can receive an ACTION_OUTSIDE event the with valid
+ * coordinates, after these, device A and device B continue report MOVE event, the right and left
+ * window can receive it, but outside window event can't receive it.
+ */
+TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinatesWhenMultiDevice) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect{0, 0, 100, 100});
+    leftWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+
+    sp<FakeWindowHandle> outsideWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Outside Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    outsideWindow->setFrame(Rect{100, 100, 200, 200});
+    outsideWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+    outsideWindow->setWatchOutsideTouch(true);
+
+    std::shared_ptr<FakeApplicationHandle> anotherApplication =
+            std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect{100, 0, 200, 100});
+    rightWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+
+    // OutsideWindow must be above left window and right window to receive ACTION_OUTSIDE events
+    // when left window or right window is tapped
+    mDispatcher->onWindowInfosChanged(
+            {{*outsideWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on right window use device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    // Right window is belonged to another owner, so outsideWindow should receive ACTION_OUTSIDE
+    // with zeroed coords.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceA), WithCoords(0, 0)));
+
+    // Tap on left window use device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+    // Because new gesture down on the left window that has the same owner with outside Window, the
+    // outside Window should receive the ACTION_OUTSIDE with coords.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceB), WithCoords(-50, -50)));
+
+    // Ensure that windows that can only accept outside do not receive remaining gestures
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(51).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+    outsideWindow->assertNoEvents();
+}
+
+/**
  * This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when
  * a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one
  * ACTION_OUTSIDE event is sent per gesture.
@@ -4374,32 +5225,33 @@
 TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) {
     // There are three windows that do not overlap. `window` wants to WATCH_OUTSIDE_TOUCH.
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "First Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setWatchOutsideTouch(true);
     window->setFrame(Rect{0, 0, 100, 100});
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setFrame(Rect{100, 100, 200, 200});
     sp<FakeWindowHandle> thirdWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Third Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     thirdWindow->setFrame(Rect{200, 200, 300, 300});
     mDispatcher->onWindowInfosChanged(
             {{*window->getInfo(), *secondWindow->getInfo(), *thirdWindow->getInfo()}, {}, 0, 0});
 
     // First pointer lands outside all windows. `window` does not get ACTION_OUTSIDE.
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {PointF{-10, -10}}));
+    mDispatcher->notifyMotion(
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {PointF{-10, -10}}));
     window->assertNoEvents();
     secondWindow->assertNoEvents();
 
     // The second pointer lands inside `secondWindow`, which should receive a DOWN event.
     // Now, `window` should get ACTION_OUTSIDE.
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {PointF{-10, -10}, PointF{105, 105}}));
     const std::map<int32_t, PointF> expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}};
     window->consumeMotionEvent(
@@ -4410,7 +5262,8 @@
     // The third pointer lands inside `thirdWindow`, which should receive a DOWN event. There is
     // no ACTION_OUTSIDE sent to `window` because one has already been sent for this gesture.
     mDispatcher->notifyMotion(
-            generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT,
                                {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}}));
     window->assertNoEvents();
     secondWindow->consumeMotionMove();
@@ -4419,8 +5272,9 @@
 
 TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4428,13 +5282,15 @@
 
     window->consumeFocusEvent(true);
 
-    const NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    const NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    const NotifyKeyArgs keyDown =
+            generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT);
+    const NotifyKeyArgs keyUp =
+            generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT);
     mDispatcher->notifyKey(keyDown);
     mDispatcher->notifyKey(keyUp);
 
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 
     // All windows are removed from the display. Ensure that we can no longer dispatch to it.
     mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
@@ -4448,22 +5304,23 @@
 
 TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     // Ensure window is non-split and have some transform.
     window->setPreventSplitting(true);
     window->setWindowOffset(20, 40);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(-30).y(-50))
@@ -4473,7 +5330,8 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    const MotionEvent* event = window->consumeMotion();
+    std::unique_ptr<MotionEvent> event = window->consumeMotionEvent();
+    ASSERT_NE(nullptr, event);
     EXPECT_EQ(POINTER_1_DOWN, event->getAction());
     EXPECT_EQ(70, event->getX(0));  // 50 + 20
     EXPECT_EQ(90, event->getY(0));  // 50 + 40
@@ -4493,12 +5351,12 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Left splittable Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     leftWindow->setPreventSplitting(false);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
     sp<FakeWindowHandle> rightWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Right non-splittable Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     rightWindow->setPreventSplitting(true);
     rightWindow->setFrame(Rect(100, 100, 200, 200));
     mDispatcher->onWindowInfosChanged(
@@ -4521,12 +5379,12 @@
 
 TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
     sp<FakeWindowHandle> trustedOverlay =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Trusted Overlay",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     trustedOverlay->setSpy(true);
     trustedOverlay->setTrustedOverlay(true);
 
@@ -4589,8 +5447,8 @@
 
 TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeNotSentToSingleWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4646,8 +5504,8 @@
  */
 TEST_F(InputDispatcherTest, MultiplePointersWithRotatingWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4712,13 +5570,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> spyWindowDefaultDisplay =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                       ui::LogicalDisplayId::DEFAULT);
     spyWindowDefaultDisplay->setTrustedOverlay(true);
     spyWindowDefaultDisplay->setSpy(true);
 
     sp<FakeWindowHandle> windowDefaultDisplay =
             sp<FakeWindowHandle>::make(application, mDispatcher, "DefaultDisplay",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     windowDefaultDisplay->setWindowTransform(1, 0, 0, 1);
 
     sp<FakeWindowHandle> windowSecondDisplay = windowDefaultDisplay->clone(SECOND_DISPLAY_ID);
@@ -4732,10 +5591,10 @@
              0,
              0});
 
-    // Send down to ADISPLAY_ID_DEFAULT
+    // Send down to ui::LogicalDisplayId::DEFAULT
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 100}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     spyWindowDefaultDisplay->consumeMotionDown();
@@ -4744,13 +5603,14 @@
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindowDefaultDisplay->getToken()));
 
     // windowDefaultDisplay gets cancel
-    MotionEvent* event = windowDefaultDisplay->consumeMotion();
+    std::unique_ptr<MotionEvent> event = windowDefaultDisplay->consumeMotionEvent();
+    ASSERT_NE(nullptr, event);
     EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction());
 
-    // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so the
-    // coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y
-    // coordinates are both 100, otherwise if the cancel event is sent to windowSecondDisplay of
-    // SECOND_DISPLAY_ID, the x and y coordinates are 200
+    // The cancel event is sent to windowDefaultDisplay of the ui::LogicalDisplayId::DEFAULT
+    // display, so the coordinates of the cancel are converted by windowDefaultDisplay's transform,
+    // the x and y coordinates are both 100, otherwise if the cancel event is sent to
+    // windowSecondDisplay of SECOND_DISPLAY_ID, the x and y coordinates are 200
     EXPECT_EQ(100, event->getX(0));
     EXPECT_EQ(100, event->getY(0));
 }
@@ -4769,7 +5629,7 @@
         removeAllWindowsAndDisplays();
     }
 
-    void addDisplayInfo(int displayId, const ui::Transform& transform) {
+    void addDisplayInfo(ui::LogicalDisplayId displayId, const ui::Transform& transform) {
         gui::DisplayInfo info;
         info.displayId = displayId;
         info.transform = transform;
@@ -4794,7 +5654,7 @@
         // respectively.
         ui::Transform displayTransform;
         displayTransform.set(2, 0, 0, 4);
-        addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+        addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform);
 
         std::shared_ptr<FakeApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
@@ -4802,13 +5662,13 @@
         // Add two windows to the display. Their frames are represented in the display space.
         sp<FakeWindowHandle> firstWindow =
                 sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                           ADISPLAY_ID_DEFAULT);
+                                           ui::LogicalDisplayId::DEFAULT);
         firstWindow->setFrame(Rect(0, 0, 100, 200), displayTransform);
         addWindow(firstWindow);
 
         sp<FakeWindowHandle> secondWindow =
                 sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                           ADISPLAY_ID_DEFAULT);
+                                           ui::LogicalDisplayId::DEFAULT);
         secondWindow->setFrame(Rect(100, 200, 200, 400), displayTransform);
         addWindow(secondWindow);
         return {std::move(firstWindow), std::move(secondWindow)};
@@ -4825,8 +5685,8 @@
     // selected so that if the hit test was performed with the point and the bounds being in
     // different coordinate spaces, the event would end up in the incorrect window.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {PointF{75, 55}}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{75, 55}}));
 
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
@@ -4839,7 +5699,7 @@
     // Send down to the first window. The point is represented in the logical display space. The
     // point is selected so that if the hit test was done in logical display space, then it would
     // end up in the incorrect window.
-    injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                      PointF{75 * 2, 55 * 4});
 
     firstWindow->consumeMotionDown();
@@ -4858,7 +5718,7 @@
     const vec2 untransformedPoint = injectedEventTransform.inverse().transform(expectedPoint);
 
     MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                                .displayId(ADISPLAY_ID_DEFAULT)
+                                .displayId(ui::LogicalDisplayId::DEFAULT)
                                 .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                                 .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER)
                                                  .x(untransformedPoint.x)
@@ -4877,12 +5737,12 @@
     auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
 
     // Send down to the second window.
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {PointF{150, 220}}));
+    mDispatcher->notifyMotion(
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {PointF{150, 220}}));
 
     firstWindow->assertNoEvents();
-    const MotionEvent* event = secondWindow->consumeMotion();
+    std::unique_ptr<MotionEvent> event = secondWindow->consumeMotionEvent();
     ASSERT_NE(nullptr, event);
     EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction());
 
@@ -4901,17 +5761,17 @@
     auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
     // The monitor will always receive events in the logical display's coordinate space, because
     // it does not have a window.
-    FakeMonitorReceiver monitor{*mDispatcher, "Monitor", ADISPLAY_ID_DEFAULT};
+    FakeMonitorReceiver monitor{*mDispatcher, "Monitor", ui::LogicalDisplayId::DEFAULT};
 
     // Send down to the first window.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{50, 100}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{50, 100}}));
     firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
     monitor.consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
 
     // Second pointer goes down on second window.
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {PointF{50, 100}, PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80)));
     const std::map<int32_t, PointF> expectedMonitorPointers{{0, PointF{100, 400}},
@@ -4932,12 +5792,12 @@
 
     // Send down to the first window.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{50, 100}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{50, 100}}));
     firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
 
     // The pointer is transferred to the second window, and the second window receives it in the
     // correct coordinate space.
-    mDispatcher->transferTouchFocus(firstWindow->getToken(), secondWindow->getToken());
+    mDispatcher->transferTouchGesture(firstWindow->getToken(), secondWindow->getToken());
     firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithCoords(100, 400)));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(-100, -400)));
 }
@@ -4947,13 +5807,15 @@
 
     // Send hover move to the second window, and ensure it shows up as hover enter.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                            WithCoords(100, 80), WithRawCoords(300, 880)));
 
     // Touch down at the same location and ensure a hover exit is synthesized.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
                                            WithRawCoords(300, 880)));
     secondWindow->consumeMotionEvent(
@@ -4978,14 +5840,16 @@
 
     // Send hover move to the second window, and ensure it shows up as hover enter.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                            WithCoords(100, 80), WithRawCoords(300, 880)));
 
     // Touch down at the same location and ensure a hover exit is synthesized for the correct
     // display.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
                                            WithRawCoords(300, 880)));
     secondWindow->consumeMotionEvent(
@@ -4999,7 +5863,8 @@
 
     // Send hover enter to second window
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                            WithCoords(100, 80), WithRawCoords(300, 880)));
 
@@ -5027,17 +5892,18 @@
 
     // Send hover enter to second window
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                            WithCoords(100, 80), WithRawCoords(300, 880),
-                                           WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                                           WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
 
     mDispatcher->cancelCurrentTouch();
 
     // Ensure the cancelation happens with the correct displayId and the correct coordinates.
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
                                            WithRawCoords(300, 880),
-                                           WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                                           WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     secondWindow->assertNoEvents();
     firstWindow->assertNoEvents();
 }
@@ -5064,13 +5930,13 @@
     const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
     const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
                                          logicalDisplayWidth, logicalDisplayHeight);
-    addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+    addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform);
 
     // Create a window with its bounds determined in the logical display.
     const Rect frameInLogicalDisplay(100, 100, 200, 300);
     const Rect frameInDisplay = displayTransform.inverse().transform(frameInLogicalDisplay);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(frameInDisplay, displayTransform);
     addWindow(window);
 
@@ -5080,14 +5946,14 @@
     for (const auto pointInsideWindow : insidePoints) {
         const vec2 p = displayTransform.inverse().transform(pointInsideWindow);
         const PointF pointInDisplaySpace{p.x, p.y};
-        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
         window->consumeMotionDown();
 
-        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
         window->consumeMotionUp();
     }
 
@@ -5097,17 +5963,105 @@
     for (const auto pointOutsideWindow : outsidePoints) {
         const vec2 p = displayTransform.inverse().transform(pointOutsideWindow);
         const PointF pointInDisplaySpace{p.x, p.y};
-        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
 
-        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
     }
     window->assertNoEvents();
 }
 
+// This test verifies the occlusion detection for all rotations of the display by tapping
+// in different locations on the display, specifically points close to the four corners of a
+// window.
+TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOrientations) {
+    constexpr static int32_t displayWidth = 400;
+    constexpr static int32_t displayHeight = 800;
+
+    std::shared_ptr<FakeApplicationHandle> untrustedWindowApplication =
+            std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    const auto rotation = GetParam();
+
+    // Set up the display with the specified rotation.
+    const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270;
+    const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth;
+    const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
+    const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
+                                         logicalDisplayWidth, logicalDisplayHeight);
+    addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform);
+
+    // Create a window that not trusted.
+    const Rect untrustedWindowFrameInLogicalDisplay(100, 100, 200, 300);
+
+    const Rect untrustedWindowFrameInDisplay =
+            displayTransform.inverse().transform(untrustedWindowFrameInLogicalDisplay);
+
+    sp<FakeWindowHandle> untrustedWindow =
+            sp<FakeWindowHandle>::make(untrustedWindowApplication, mDispatcher, "UntrustedWindow",
+                                       ui::LogicalDisplayId::DEFAULT);
+    untrustedWindow->setFrame(untrustedWindowFrameInDisplay, displayTransform);
+    untrustedWindow->setTrustedOverlay(false);
+    untrustedWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
+    untrustedWindow->setTouchable(false);
+    untrustedWindow->setAlpha(1.0f);
+    untrustedWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+    addWindow(untrustedWindow);
+
+    // Create a simple app window below the untrusted window.
+    const Rect simpleAppWindowFrameInLogicalDisplay(0, 0, 300, 600);
+    const Rect simpleAppWindowFrameInDisplay =
+            displayTransform.inverse().transform(simpleAppWindowFrameInLogicalDisplay);
+
+    sp<FakeWindowHandle> simpleAppWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "SimpleAppWindow",
+                                       ui::LogicalDisplayId::DEFAULT);
+    simpleAppWindow->setFrame(simpleAppWindowFrameInDisplay, displayTransform);
+    simpleAppWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+    addWindow(simpleAppWindow);
+
+    // The following points in logical display space should be inside the untrusted window, so
+    // the simple window could not receive events that coordinate is these point.
+    static const std::array<vec2, 4> untrustedPoints{
+            {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}};
+
+    for (const auto untrustedPoint : untrustedPoints) {
+        const vec2 p = displayTransform.inverse().transform(untrustedPoint);
+        const PointF pointInDisplaySpace{p.x, p.y};
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
+    }
+    untrustedWindow->assertNoEvents();
+    simpleAppWindow->assertNoEvents();
+    // The following points in logical display space should be outside the untrusted window, so
+    // the simple window should receive events that coordinate is these point.
+    static const std::array<vec2, 5> trustedPoints{
+            {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}};
+    for (const auto trustedPoint : trustedPoints) {
+        const vec2 p = displayTransform.inverse().transform(trustedPoint);
+        const PointF pointInDisplaySpace{p.x, p.y};
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
+        simpleAppWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
+        simpleAppWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                         AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+    }
+    untrustedWindow->assertNoEvents();
+}
+
 // Run the precision tests for all rotations.
 INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests,
                          InputDispatcherDisplayOrientationFixture,
@@ -5129,26 +6083,32 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setDupTouchToWallpaper(true);
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> wallpaper =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaper->setIsWallpaper(true);
-    // Add the windows to the dispatcher
+    // Add the windows to the dispatcher, and ensure the first window is focused
     mDispatcher->onWindowInfosChanged(
             {{*firstWindow->getInfo(), *secondWindow->getInfo(), *wallpaper->getInfo()}, {}, 0, 0});
+    setFocusedWindow(firstWindow);
+    firstWindow->consumeFocusEvent(true);
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
 
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
-    wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
+    // Dispatcher reports pointer down outside focus for the wallpaper
+    mFakePolicy->assertOnPointerDownEquals(wallpaper->getToken());
 
     // Transfer touch to the second window
     TransferFunction f = GetParam();
@@ -5156,42 +6116,47 @@
     ASSERT_TRUE(success);
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionDown();
-    wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    wallpaper->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
+    // There should not be any changes to the focused window when transferring touch
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertOnPointerDownWasNotCalled());
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
-    // The first  window gets no events and the second gets up
+                                                 ui::LogicalDisplayId::DEFAULT));
+    // The first window gets no events and the second gets up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionUp();
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     wallpaper->assertNoEvents();
 }
 
 /**
- * When 'transferTouch' API is invoked, dispatcher needs to find the "best" window to take touch
- * from. When we have spy windows, there are several windows to choose from: either spy, or the
- * 'real' (non-spy) window. Always prefer the 'real' window because that's what would be most
+ * When 'transferTouchGesture' API is invoked, dispatcher needs to find the "best" window to take
+ * touch from. When we have spy windows, there are several windows to choose from: either spy, or
+ * the 'real' (non-spy) window. Always prefer the 'real' window because that's what would be most
  * natural to the user.
  * In this test, we are sending a pointer to both spy window and first window. We then try to
  * transfer touch to the second window. The dispatcher should identify the first window as the
  * one that should lose the gesture, and therefore the action should be to move the gesture from
  * the first window to the second.
- * The main goal here is to test the behaviour of 'transferTouch' API, but it's still valid to test
- * the other API, as well.
+ * The main goal here is to test the behaviour of 'transferTouchGesture' API, but it's still valid
+ * to test the other API, as well.
  */
 TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     // Create a couple of windows + a spy window
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> firstWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> firstWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "First",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> secondWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     // Add the windows to the dispatcher
     mDispatcher->onWindowInfosChanged(
@@ -5199,27 +6164,30 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
     // Only the first window and spy should get the down event
     spyWindow->consumeMotionDown();
     firstWindow->consumeMotionDown();
 
     // Transfer touch to the second window. Non-spy window should be preferred over the spy window
-    // if f === 'transferTouch'.
+    // if f === 'transferTouchGesture'.
     TransferFunction f = GetParam();
     const bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
     ASSERT_TRUE(success);
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionDown();
+    secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first  window gets no events and the second+spy get up
     firstWindow->assertNoEvents();
     spyWindow->consumeMotionUp();
-    secondWindow->consumeMotionUp();
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) {
@@ -5230,11 +6198,11 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setPreventSplitting(true);
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setPreventSplitting(true);
 
     // Add the windows to the dispatcher
@@ -5243,15 +6211,16 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {touchPoint}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {touchPoint}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send pointer down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {touchPoint, touchPoint}));
     // Only the first window should get the pointer down event
     firstWindow->consumeMotionPointerDown(1);
     secondWindow->assertNoEvents();
@@ -5262,22 +6231,27 @@
     ASSERT_TRUE(success);
     // The first window gets cancel and the second gets down and pointer down
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionDown();
-    secondWindow->consumeMotionPointerDown(1);
+    secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionPointerDown(1, ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send pointer up to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {touchPoint, touchPoint}));
     // The first window gets nothing and the second gets pointer up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionPointerUp(1);
+    secondWindow->consumeMotionPointerUp(1, ui::LogicalDisplayId::DEFAULT,
+                                         AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first window gets nothing and the second gets up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionUp();
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) {
@@ -5286,19 +6260,21 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setDupTouchToWallpaper(true);
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setDupTouchToWallpaper(true);
 
     sp<FakeWindowHandle> wallpaper1 =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaper1->setIsWallpaper(true);
 
     sp<FakeWindowHandle> wallpaper2 =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaper2->setIsWallpaper(true);
     // Add the windows to the dispatcher
     mDispatcher->onWindowInfosChanged({{*firstWindow->getInfo(), *wallpaper1->getInfo(),
@@ -5309,12 +6285,13 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
 
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
-    wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper1->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
     wallpaper2->assertNoEvents();
 
     // Transfer touch focus to the second window
@@ -5324,109 +6301,52 @@
 
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionDown();
-    wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
-    wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    wallpaper1->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
+    wallpaper2->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                  EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first  window gets no events and the second gets up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionUp();
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     wallpaper1->assertNoEvents();
-    wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper2->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 // For the cases of single pointer touch and two pointers non-split touch, the api's
-// 'transferTouch' and 'transferTouchFocus' are equivalent in behaviour. They only differ
+// 'transferTouchGesture' and 'transferTouchOnDisplay' are equivalent in behaviour. They only differ
 // for the case where there are multiple pointers split across several windows.
-INSTANTIATE_TEST_SUITE_P(TransferFunctionTests, TransferTouchFixture,
-                         ::testing::Values(
-                                 [&](const std::unique_ptr<InputDispatcher>& dispatcher,
-                                     sp<IBinder> /*ignored*/, sp<IBinder> destChannelToken) {
-                                     return dispatcher->transferTouch(destChannelToken,
-                                                                      ADISPLAY_ID_DEFAULT);
-                                 },
-                                 [&](const std::unique_ptr<InputDispatcher>& dispatcher,
-                                     sp<IBinder> from, sp<IBinder> to) {
-                                     return dispatcher->transferTouchFocus(from, to,
-                                                                           /*isDragAndDrop=*/false);
-                                 }));
+INSTANTIATE_TEST_SUITE_P(
+        InputDispatcherTransferFunctionTests, TransferTouchFixture,
+        ::testing::Values(
+                [&](const std::unique_ptr<InputDispatcher>& dispatcher, sp<IBinder> /*ignored*/,
+                    sp<IBinder> destChannelToken) {
+                    return dispatcher->transferTouchOnDisplay(destChannelToken,
+                                                              ui::LogicalDisplayId::DEFAULT);
+                },
+                [&](const std::unique_ptr<InputDispatcher>& dispatcher, sp<IBinder> from,
+                    sp<IBinder> to) {
+                    return dispatcher->transferTouchGesture(from, to,
+                                                            /*isDragAndDrop=*/false);
+                }));
 
-TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) {
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-
-    sp<FakeWindowHandle> firstWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
-    firstWindow->setFrame(Rect(0, 0, 600, 400));
-
-    sp<FakeWindowHandle> secondWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
-    secondWindow->setFrame(Rect(0, 400, 600, 800));
-
-    // Add the windows to the dispatcher
-    mDispatcher->onWindowInfosChanged(
-            {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
-
-    PointF pointInFirst = {300, 200};
-    PointF pointInSecond = {300, 600};
-
-    // Send down to the first window
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {pointInFirst}));
-    // Only the first window should get the down event
-    firstWindow->consumeMotionDown();
-    secondWindow->assertNoEvents();
-
-    // Send down to the second window
-    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
-                                                 {pointInFirst, pointInSecond}));
-    // The first window gets a move and the second a down
-    firstWindow->consumeMotionMove();
-    secondWindow->consumeMotionDown();
-
-    // Transfer touch focus to the second window
-    mDispatcher->transferTouchFocus(firstWindow->getToken(), secondWindow->getToken());
-    // The first window gets cancel and the new gets pointer down (it already saw down)
-    firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionPointerDown(1);
-
-    // Send pointer up to the second window
-    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
-                                                 {pointInFirst, pointInSecond}));
-    // The first window gets nothing and the second gets pointer up
-    firstWindow->assertNoEvents();
-    secondWindow->consumeMotionPointerUp(1);
-
-    // Send up event to the second window
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
-    // The first window gets nothing and the second gets up
-    firstWindow->assertNoEvents();
-    secondWindow->consumeMotionUp();
-}
-
-// Same as TransferTouchFocus_TwoPointersSplitTouch, but using 'transferTouch' api.
-// Unlike 'transferTouchFocus', calling 'transferTouch' when there are two windows receiving
-// touch is not supported, so the touch should continue on those windows and the transferred-to
-// window should get nothing.
 TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setFrame(Rect(0, 0, 600, 400));
 
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setFrame(Rect(0, 400, 600, 800));
 
     // Add the windows to the dispatcher
@@ -5438,24 +6358,89 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {pointInFirst}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {pointInFirst}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send down to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {pointInFirst, pointInSecond}));
+    // The first window gets a move and the second a down
+    firstWindow->consumeMotionMove();
+    secondWindow->consumeMotionDown();
+
+    // Transfer touch to the second window
+    mDispatcher->transferTouchGesture(firstWindow->getToken(), secondWindow->getToken());
+    // The first window gets cancel and the new gets pointer down (it already saw down)
+    firstWindow->consumeMotionCancel();
+    secondWindow->consumeMotionPointerDown(1, ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+
+    // Send pointer up to the second window
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {pointInFirst, pointInSecond}));
+    // The first window gets nothing and the second gets pointer up
+    firstWindow->assertNoEvents();
+    secondWindow->consumeMotionPointerUp(1, ui::LogicalDisplayId::DEFAULT,
+                                         AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+
+    // Send up event to the second window
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
+    // The first window gets nothing and the second gets up
+    firstWindow->assertNoEvents();
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+}
+
+// Same as TransferTouch_TwoPointersSplitTouch, but using 'transferTouchOnDisplay' api.
+// Unlike 'transferTouchGesture', calling 'transferTouchOnDisplay' when there are two windows
+// receiving touch is not supported, so the touch should continue on those windows and the
+// transferred-to window should get nothing.
+TEST_F(InputDispatcherTest, TransferTouchOnDisplay_TwoPointersSplitTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> firstWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    firstWindow->setFrame(Rect(0, 0, 600, 400));
+
+    sp<FakeWindowHandle> secondWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    secondWindow->setFrame(Rect(0, 400, 600, 800));
+
+    // Add the windows to the dispatcher
+    mDispatcher->onWindowInfosChanged(
+            {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
+
+    PointF pointInFirst = {300, 200};
+    PointF pointInSecond = {300, 600};
+
+    // Send down to the first window
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {pointInFirst}));
+    // Only the first window should get the down event
+    firstWindow->consumeMotionDown();
+    secondWindow->assertNoEvents();
+
+    // Send down to the second window
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {pointInFirst, pointInSecond}));
     // The first window gets a move and the second a down
     firstWindow->consumeMotionMove();
     secondWindow->consumeMotionDown();
 
     // Transfer touch focus to the second window
-    const bool transferred =
-            mDispatcher->transferTouch(secondWindow->getToken(), ADISPLAY_ID_DEFAULT);
-    // The 'transferTouch' call should not succeed, because there are 2 touched windows
+    const bool transferred = mDispatcher->transferTouchOnDisplay(secondWindow->getToken(),
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    // The 'transferTouchOnDisplay' call should not succeed, because there are 2 touched windows
     ASSERT_FALSE(transferred);
     firstWindow->assertNoEvents();
     secondWindow->assertNoEvents();
@@ -5463,7 +6448,7 @@
     // The rest of the dispatch should proceed as normal
     // Send pointer up to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {pointInFirst, pointInSecond}));
     // The first window gets MOVE and the second gets pointer up
     firstWindow->consumeMotionMove();
@@ -5471,25 +6456,28 @@
 
     // Send up event to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first window gets nothing and the second gets up
     firstWindow->consumeMotionUp();
     secondWindow->assertNoEvents();
 }
 
 // This case will create two windows and one mirrored window on the default display and mirror
-// two windows on the second display. It will test if 'transferTouchFocus' works fine if we put
+// two windows on the second display. It will test if 'transferTouchGesture' works fine if we put
 // the windows info of second display before default display.
-TEST_F(InputDispatcherTest, TransferTouchFocus_CloneSurface) {
+TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> firstWindowInPrimary =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1",
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100));
     sp<FakeWindowHandle> secondWindowInPrimary =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2",
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
 
-    sp<FakeWindowHandle> mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> mirrorWindowInPrimary =
+            firstWindowInPrimary->clone(ui::LogicalDisplayId::DEFAULT);
     mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
 
     sp<FakeWindowHandle> firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID);
@@ -5508,47 +6496,53 @@
              0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    firstWindowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    firstWindowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
-    // Transfer touch focus
-    ASSERT_TRUE(mDispatcher->transferTouchFocus(firstWindowInPrimary->getToken(),
-                                                secondWindowInPrimary->getToken()));
+    // Transfer touch
+    ASSERT_TRUE(mDispatcher->transferTouchGesture(firstWindowInPrimary->getToken(),
+                                                  secondWindowInPrimary->getToken()));
     // The first window gets cancel.
     firstWindowInPrimary->consumeMotionCancel();
-    secondWindowInPrimary->consumeMotionDown();
+    secondWindowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                             AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     firstWindowInPrimary->assertNoEvents();
-    secondWindowInPrimary->consumeMotionMove();
+    secondWindowInPrimary->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                             AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     firstWindowInPrimary->assertNoEvents();
-    secondWindowInPrimary->consumeMotionUp();
+    secondWindowInPrimary->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
-// Same as TransferTouchFocus_CloneSurface, but this touch on the secondary display and use
-// 'transferTouch' api.
-TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) {
+// Same as TransferTouch_CloneSurface, but this touch on the secondary display and use
+// 'transferTouchOnDisplay' api.
+TEST_F(InputDispatcherTest, TransferTouchOnDisplay_CloneSurface) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> firstWindowInPrimary =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1",
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100));
     sp<FakeWindowHandle> secondWindowInPrimary =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2",
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
 
-    sp<FakeWindowHandle> mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> mirrorWindowInPrimary =
+            firstWindowInPrimary->clone(ui::LogicalDisplayId::DEFAULT);
     mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
 
     sp<FakeWindowHandle> firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID);
@@ -5573,33 +6567,37 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    firstWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID);
+    firstWindowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID);
 
     // Transfer touch focus
-    ASSERT_TRUE(mDispatcher->transferTouch(secondWindowInSecondary->getToken(), SECOND_DISPLAY_ID));
+    ASSERT_TRUE(mDispatcher->transferTouchOnDisplay(secondWindowInSecondary->getToken(),
+                                                    SECOND_DISPLAY_ID));
 
     // The first window gets cancel.
-    firstWindowInPrimary->consumeMotionCancel(SECOND_DISPLAY_ID);
-    secondWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID);
+    firstWindowInSecondary->consumeMotionCancel(SECOND_DISPLAY_ID);
+    secondWindowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID,
+                                               AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 SECOND_DISPLAY_ID, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    firstWindowInPrimary->assertNoEvents();
-    secondWindowInPrimary->consumeMotionMove(SECOND_DISPLAY_ID);
+    firstWindowInSecondary->assertNoEvents();
+    secondWindowInSecondary->consumeMotionMove(SECOND_DISPLAY_ID,
+                                               AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    firstWindowInPrimary->assertNoEvents();
-    secondWindowInPrimary->consumeMotionUp(SECOND_DISPLAY_ID);
+    firstWindowInSecondary->assertNoEvents();
+    secondWindowInSecondary->consumeMotionUp(SECOND_DISPLAY_ID, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -5607,10 +6605,10 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive key down event.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // Should have poked user activity
     mDispatcher->waitForIdle();
@@ -5619,8 +6617,9 @@
 
 TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setDisableUserActivity(true);
     window->setFocusable(true);
@@ -5629,10 +6628,10 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive key down event.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // Should have poked user activity
     mDispatcher->waitForIdle();
@@ -5641,8 +6640,9 @@
 
 TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -5650,7 +6650,8 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(
+            generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
 
     // System key is not passed down
@@ -5662,8 +6663,9 @@
 
 TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -5671,7 +6673,8 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(
+            generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
 
     // System key is not passed down
@@ -5683,8 +6686,9 @@
 
 TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setDisableUserActivity(true);
     window->setFocusable(true);
@@ -5693,7 +6697,8 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(
+            generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
 
     // System key is not passed down
@@ -5705,18 +6710,19 @@
 
 TEST_F(InputDispatcherTest, InjectedTouchesPokeUserActivity) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {100, 100}))
+                                ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
 
     // Should have poked user activity
     mDispatcher->waitForIdle();
@@ -5725,12 +6731,13 @@
 
 TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
 
     window->assertNoEvents();
@@ -5739,19 +6746,21 @@
 // If a window is touchable, but does not have focus, it should receive motion events, but not keys
 TEST_F(InputDispatcherTest, UnfocusedWindow_ReceivesMotionsButNotKeys) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Send key
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     // Send motion
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive only the motion event
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     window->assertNoEvents(); // Key event or focus event will not be received
 }
 
@@ -5760,12 +6769,12 @@
 
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setFrame(Rect(0, 0, 600, 400));
 
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setFrame(Rect(0, 400, 600, 800));
 
     // Add the windows to the dispatcher
@@ -5777,15 +6786,15 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {pointInFirst}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {pointInFirst}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send down to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {pointInFirst, pointInSecond}));
     // The first window gets a move and the second a down
     firstWindow->consumeMotionMove();
@@ -5793,17 +6802,17 @@
 
     // Send pointer cancel to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {pointInFirst, pointInSecond});
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {pointInFirst, pointInSecond});
     pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
     mDispatcher->notifyMotion(pointerUpMotionArgs);
     // The first window gets move and the second gets cancel.
-    firstWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
-    secondWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+    firstWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+    secondWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
 
     // Send up event.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first window gets up and the second gets nothing.
     firstWindow->consumeMotionUp();
     secondWindow->assertNoEvents();
@@ -5812,8 +6821,8 @@
 TEST_F(InputDispatcherTest, SendTimeline_DoesNotCrashDispatcher) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
     graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
@@ -5836,28 +6845,29 @@
  */
 TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDisappears) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                                             ui::LogicalDisplayId::DEFAULT);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both the foreground window and the global monitor should receive the touch down
     window->consumeMotionDown();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     window->consumeMotionMove();
-    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
 
     // Now the foreground window goes away
     mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
@@ -5868,41 +6878,47 @@
     // cause a cancel for the monitor, as well.
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {120, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {120, 200}))
             << "Injection should fail because the window was removed";
     window->assertNoEvents();
     // Global monitor now gets the cancel
-    monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherMonitorTest, ReceivesMotionEvents) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) {
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // Pilfer pointers from the monitor.
     // This should not do anything and the window should continue to receive events.
@@ -5910,56 +6926,279 @@
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT))
+                                ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
-    window->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
+    window->consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherMonitorTest, NoWindowTransform) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     window->setWindowOffset(20, 40);
     window->setWindowTransform(0, 1, -1, 0);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    MotionEvent* event = monitor.consumeMotion();
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    std::unique_ptr<MotionEvent> event = monitor.consumeMotion();
+    ASSERT_NE(nullptr, event);
     // Even though window has transform, gesture monitor must not.
     ASSERT_EQ(ui::Transform(), event->getTransform());
 }
 
 TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Injection should fail if there is a monitor, but no touchable window";
     monitor.assertNoEvents();
 }
 
+/**
+ * Two displays
+ * The first monitor has a foreground window, a monitor
+ * The second window has only one monitor.
+ * We first inject a Down event into the first display, this injection should succeed and both
+ * the foreground window and monitor should receive a down event, then inject a Down event into
+ * the second display as well, this injection should fail, at this point, the first display
+ * window and monitor should not receive a cancel or any other event.
+ * Continue to inject Move and UP events to the first display, the events should be received
+ * normally by the foreground window and monitor.
+ */
+TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCanceledWhenAnotherEmptyDisplayReceiveEvents) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                                             ui::LogicalDisplayId::DEFAULT);
+
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
+    FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
+            << "The down event injected into the first display should succeed";
+
+    window->consumeMotionDown();
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
+                               {100, 200}))
+            << "The down event injected into the second display should fail since there's no "
+               "touchable window";
+
+    // Continue to inject event to first display.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ui::LogicalDisplayId::DEFAULT, {110, 220}))
+            << "The move event injected into the first display should succeed";
+
+    window->consumeMotionMove();
+    monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                             {110, 220}))
+            << "The up event injected into the first display should succeed";
+
+    window->consumeMotionUp();
+    monitor.consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
+
+    window->assertNoEvents();
+    monitor.assertNoEvents();
+    secondMonitor.assertNoEvents();
+}
+
+/**
+ * Two displays
+ * There is a monitor and foreground window on each display.
+ * First, we inject down events into each of the two displays, at this point, the foreground windows
+ * and monitors on both displays should receive down events.
+ * At this point, the foreground window of the second display goes away, the gone window should
+ * receive the cancel event, and the other windows and monitors should not receive any events.
+ * Inject a move event into the second display. At this point, the injection should fail because
+ * the second display no longer has a foreground window. At this point, the monitor on the second
+ * display should receive a cancel event, and any windows or monitors on the first display should
+ * not receive any events, and any subsequent injection of events into the second display should
+ * also fail.
+ * Continue to inject events into the first display, and the events should all be injected
+ * successfully and received normally.
+ */
+TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCancelWhenAnotherDisplayMonitorTouchCanceled) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    sp<FakeWindowHandle> secondWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "SecondForeground",
+                                       SECOND_DISPLAY_ID);
+
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
+    FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
+
+    // There is a foreground window on both displays.
+    mDispatcher->onWindowInfosChanged({{*window->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
+            << "The down event injected into the first display should succeed";
+
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
+                               {100, 200}))
+            << "The down event injected into the second display should succeed";
+
+    secondWindow->consumeMotionDown(SECOND_DISPLAY_ID);
+    secondMonitor.consumeMotionDown(SECOND_DISPLAY_ID);
+
+    // Now second window is gone away.
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // The gone window should receive a cancel, and the monitor on the second display should not
+    // receive any events.
+    secondWindow->consumeMotionCancel(SECOND_DISPLAY_ID);
+    secondMonitor.assertNoEvents();
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                SECOND_DISPLAY_ID, {110, 220}))
+            << "The move event injected into the second display should fail because there's no "
+               "touchable window";
+    // Now the monitor on the second display should receive a cancel event.
+    secondMonitor.consumeMotionCancel(SECOND_DISPLAY_ID);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ui::LogicalDisplayId::DEFAULT, {110, 200}))
+            << "The move event injected into the first display should succeed";
+
+    window->consumeMotionMove();
+    monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
+                             {110, 220}))
+            << "The up event injected into the second display should fail because there's no "
+               "touchable window";
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                             {110, 220}))
+            << "The up event injected into the first display should succeed";
+
+    window->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
+    monitor.consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
+
+    window->assertNoEvents();
+    monitor.assertNoEvents();
+    secondWindow->assertNoEvents();
+    secondMonitor.assertNoEvents();
+}
+
+/**
+ * One display with transform
+ * There is a foreground window and a monitor on the display
+ * Inject down event and move event sequentially, the foreground window and monitor can receive down
+ * event and move event, then let the foreground window go away, the foreground window receives
+ * cancel event, inject move event again, the monitor receives cancel event, all the events received
+ * by the monitor should be with the same transform as the display
+ */
+TEST_F(InputDispatcherMonitorTest, MonitorTouchCancelEventWithDisplayTransform) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
+
+    ui::Transform transform;
+    transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1});
+
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = ui::LogicalDisplayId::DEFAULT;
+    displayInfo.transform = transform;
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
+            << "The down event injected should succeed";
+
+    window->consumeMotionDown();
+    std::unique_ptr<MotionEvent> downMotionEvent = monitor.consumeMotion();
+    EXPECT_EQ(transform, downMotionEvent->getTransform());
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, downMotionEvent->getAction());
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ui::LogicalDisplayId::DEFAULT, {110, 220}))
+            << "The move event injected should succeed";
+
+    window->consumeMotionMove();
+    std::unique_ptr<MotionEvent> moveMotionEvent = monitor.consumeMotion();
+    EXPECT_EQ(transform, moveMotionEvent->getTransform());
+    EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, moveMotionEvent->getAction());
+
+    // Let foreground window gone
+    mDispatcher->onWindowInfosChanged({{}, {displayInfo}, 0, 0});
+
+    // Foreground window should receive a cancel event, but not the monitor.
+    window->consumeMotionCancel();
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ui::LogicalDisplayId::DEFAULT, {110, 220}))
+            << "The move event injected should failed";
+    // Now foreground should not receive any events, but monitor should receive a cancel event
+    // with transform that same as display's display.
+    std::unique_ptr<MotionEvent> cancelMotionEvent = monitor.consumeMotion();
+    EXPECT_EQ(transform, cancelMotionEvent->getTransform());
+    EXPECT_EQ(ui::LogicalDisplayId::DEFAULT, cancelMotionEvent->getDisplayId());
+    EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, cancelMotionEvent->getAction());
+
+    // Other event inject to this display should fail.
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                ui::LogicalDisplayId::DEFAULT, {110, 220}))
+            << "The up event injected should fail because the touched window was removed";
+    window->assertNoEvents();
+    monitor.assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, TestMoveEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     NotifyMotionArgs motionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
+                               ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->notifyMotion(motionArgs);
     // Window should receive motion down event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     motionArgs.action = AMOTION_EVENT_ACTION_MOVE;
     motionArgs.id += 1;
@@ -5968,8 +7207,7 @@
                                              motionArgs.pointerCoords[0].getX() - 10);
 
     mDispatcher->notifyMotion(motionArgs);
-    window->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT,
-                         /*expectedFlags=*/0);
+    window->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, /*expectedFlags=*/0);
 }
 
 /**
@@ -5979,12 +7217,13 @@
  */
 TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
     const WindowInfo& windowInfo = *window->getInfo();
 
     // Set focused application.
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
 
     SCOPED_TRACE("Check default value of touch mode");
@@ -5999,7 +7238,7 @@
 
     SCOPED_TRACE("Disable touch mode");
     mDispatcher->setInTouchMode(false, windowInfo.ownerPid, windowInfo.ownerUid,
-                                /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
+                                /*hasPermission=*/true, ui::LogicalDisplayId::DEFAULT);
     window->consumeTouchModeEvent(false);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -6013,7 +7252,7 @@
 
     SCOPED_TRACE("Enable touch mode again");
     mDispatcher->setInTouchMode(true, windowInfo.ownerPid, windowInfo.ownerUid,
-                                /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
+                                /*hasPermission=*/true, ui::LogicalDisplayId::DEFAULT);
     window->consumeTouchModeEvent(true);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -6025,10 +7264,11 @@
 
 TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -6039,9 +7279,8 @@
     const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
     mDispatcher->notifyKey(keyArgs);
 
-    KeyEvent* event = window->consumeKey();
+    std::unique_ptr<KeyEvent> event = window->consumeKey();
     ASSERT_NE(event, nullptr);
-
     std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
     ASSERT_NE(verified, nullptr);
     ASSERT_EQ(verified->type, VerifiedInputEvent::Type::KEY);
@@ -6064,28 +7303,28 @@
 
 TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     ui::Transform transform;
     transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1});
 
     gui::DisplayInfo displayInfo;
-    displayInfo.displayId = ADISPLAY_ID_DEFAULT;
+    displayInfo.displayId = ui::LogicalDisplayId::DEFAULT;
     displayInfo.transform = transform;
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0});
 
     const NotifyMotionArgs motionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
+                               ui::LogicalDisplayId::DEFAULT);
     mDispatcher->notifyMotion(motionArgs);
 
-    MotionEvent* event = window->consumeMotion();
-    ASSERT_NE(event, nullptr);
-
+    std::unique_ptr<MotionEvent> event = window->consumeMotionEvent();
+    ASSERT_NE(nullptr, event);
     std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
     ASSERT_NE(verified, nullptr);
     ASSERT_EQ(verified->type, VerifiedInputEvent::Type::MOTION);
@@ -6141,7 +7380,7 @@
     verifiedEvent.eventTimeNanos += 1;
     ASSERT_NE(initialHmac, mDispatcher->sign(verifiedEvent));
 
-    verifiedEvent.displayId += 1;
+    verifiedEvent.displayId = ui::LogicalDisplayId{verifiedEvent.displayId.val() + 1};
     ASSERT_NE(initialHmac, mDispatcher->sign(verifiedEvent));
 
     verifiedEvent.action += 1;
@@ -6168,11 +7407,12 @@
 
 TEST_F(InputDispatcherTest, SetFocusedWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowTop =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowTop = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                                ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> windowSecond =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     // Top window is also focusable but is not granted focus.
     windowTop->setFocusable(true);
@@ -6186,15 +7426,15 @@
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Focused window should receive event.
-    windowSecond->consumeKeyDown(ADISPLAY_ID_NONE);
+    windowSecond->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     windowTop->assertNoEvents();
 }
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestInvalidChannel) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     window->setFocusable(true);
     // Release channel for window is no longer valid.
@@ -6211,10 +7451,10 @@
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestNoFocusableWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(false);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
@@ -6228,11 +7468,12 @@
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowTop =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowTop = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                                ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> windowSecond =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     windowTop->setFocusable(true);
     windowSecond->setFocusable(true);
@@ -6251,16 +7492,17 @@
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Focused window should receive event.
-    windowSecond->consumeKeyDown(ADISPLAY_ID_NONE);
+    windowSecond->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 }
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowTop =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowTop = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                                ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> windowSecond =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     windowTop->setFocusable(true);
     windowSecond->setFocusable(false);
@@ -6274,18 +7516,18 @@
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Event should be dropped.
-    windowTop->consumeKeyDown(ADISPLAY_ID_NONE);
+    windowTop->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     windowSecond->assertNoEvents();
 }
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
+                                                             ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> previousFocusedWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "previousFocusedWindow",
-                                       ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+                                       ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     window->setFocusable(true);
     previousFocusedWindow->setFocusable(true);
@@ -6302,7 +7544,7 @@
     // Injected key goes to pending queue.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+                        ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE));
 
     // Window does not get focus event or key down.
     window->assertNoEvents();
@@ -6314,14 +7556,14 @@
     // Window receives focus event.
     window->consumeFocusEvent(true);
     // Focused window receives key down.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherTest, DisplayRemoved) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "window", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     // window is granted focus.
     window->setFocusable(true);
@@ -6330,7 +7572,7 @@
     window->consumeFocusEvent(true);
 
     // When a display is removed window loses focus.
-    mDispatcher->displayRemoved(ADISPLAY_ID_DEFAULT);
+    mDispatcher->displayRemoved(ui::LogicalDisplayId::DEFAULT);
     window->consumeFocusEvent(false);
 }
 
@@ -6362,10 +7604,11 @@
     constexpr gui::Uid SLIPPERY_UID{WINDOW_UID.val() + 1};
 
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     sp<FakeWindowHandle> slipperyExitWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                       ui::LogicalDisplayId::DEFAULT);
     slipperyExitWindow->setSlippery(true);
     // Make sure this one overlaps the bottom window
     slipperyExitWindow->setFrame(Rect(25, 25, 75, 75));
@@ -6374,7 +7617,8 @@
     slipperyExitWindow->setOwnerInfo(SLIPPERY_PID, SLIPPERY_UID);
 
     sp<FakeWindowHandle> slipperyEnterWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
     slipperyExitWindow->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged(
@@ -6382,20 +7626,20 @@
 
     // Use notifyMotion instead of injecting to avoid dealing with injection permissions
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {{50, 50}}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {{50, 50}}));
     slipperyExitWindow->consumeMotionDown();
     slipperyExitWindow->setFrame(Rect(70, 70, 100, 100));
     mDispatcher->onWindowInfosChanged(
             {{*slipperyExitWindow->getInfo(), *slipperyEnterWindow->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_MOVE,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {{51, 51}}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {{51, 51}}));
 
     slipperyExitWindow->consumeMotionCancel();
 
-    slipperyEnterWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT,
+    slipperyEnterWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
                                            AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
 }
 
@@ -6410,12 +7654,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> leftSlipperyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                       ui::LogicalDisplayId::DEFAULT);
     leftSlipperyWindow->setSlippery(true);
     leftSlipperyWindow->setFrame(Rect(0, 0, 100, 100));
 
     sp<FakeWindowHandle> rightDropTouchesWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                       ui::LogicalDisplayId::DEFAULT);
     rightDropTouchesWindow->setFrame(Rect(100, 0, 200, 100));
     rightDropTouchesWindow->setDropInput(true);
 
@@ -6446,12 +7692,14 @@
 TEST_F(InputDispatcherTest, InjectedTouchSlips) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> originalWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Original", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Original",
+                                       ui::LogicalDisplayId::DEFAULT);
     originalWindow->setFrame(Rect(0, 0, 200, 200));
     originalWindow->setSlippery(true);
 
     sp<FakeWindowHandle> appearingWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Appearing", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Appearing",
+                                       ui::LogicalDisplayId::DEFAULT);
     appearingWindow->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*originalWindow->getInfo()}, {}, 0, 0});
@@ -6486,24 +7734,175 @@
     appearingWindow->assertNoEvents();
 }
 
+/**
+ * Three windows:
+ * - left window, which has FLAG_SLIPPERY, so it supports slippery exit
+ * - right window
+ * - spy window
+ * The three windows do not overlap.
+ *
+ * We have two devices reporting events:
+ * - Device A reports ACTION_DOWN, which lands in the left window
+ * - Device B reports ACTION_DOWN, which lands in the spy window.
+ * - Now, device B reports ACTION_MOVE events which move to the right window.
+ *
+ * The right window should not receive any events because the spy window is not a foreground window,
+ * and also it does not support slippery touches.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSpyWindowSlipTest) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(100, 0, 200, 100));
+
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(200, 0, 300, 100));
+    spyWindow->setSpy(true);
+    spyWindow->setTrustedOverlay(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo(), *spyWindow->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on left window with device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Tap on spy window with device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to right window with device B. Touches should not slip to the right window, because spy
+    // window is not a foreground window, and it does not have FLAG_SLIPPERY
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+}
+
+/**
+ * Three windows arranged horizontally and without any overlap.
+ * The left and right windows have FLAG_SLIPPERY. The middle window does not have any special flags.
+ *
+ * We have two devices reporting events:
+ * - Device A reports ACTION_DOWN which lands in the left window
+ * - Device B reports ACTION_DOWN which lands in the right window
+ * - Device B reports ACTION_MOVE that shifts to the middle window.
+ * This should cause touches for Device B to slip from the right window to the middle window.
+ * The right window should receive ACTION_CANCEL for device B and the
+ * middle window should receive down event for Device B.
+ * If device B reports more ACTION_MOVE events, the middle window should receive remaining events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSlipperyWindowTest) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> middleWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "middle window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleWindow->setFrame(Rect(100, 0, 200, 100));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWindow->setSlippery(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *middleWindow->getInfo(), *rightWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on left window with device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Tap on right window with device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to middle window with device B. Touches should slip to middle window, because right
+    // window is a foreground window that's associated with device B and has FLAG_SLIPPERY.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to middle window with device A. Touches should slip to middle window, because left
+    // window is a foreground window that's associated with device A and has FLAG_SLIPPERY.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceA)));
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Ensure that middle window can receive the remaining move events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->assertNoEvents();
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) {
     using Uid = gui::Uid;
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
     leftWindow->setOwnerInfo(gui::Pid{1}, Uid{101});
 
     sp<FakeWindowHandle> rightSpy =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right spy", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right spy",
+                                       ui::LogicalDisplayId::DEFAULT);
     rightSpy->setFrame(Rect(100, 0, 200, 100));
     rightSpy->setOwnerInfo(gui::Pid{2}, Uid{102});
     rightSpy->setSpy(true);
     rightSpy->setTrustedOverlay(true);
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(100, 0, 200, 100));
     rightWindow->setOwnerInfo(gui::Pid{3}, Uid{103});
 
@@ -6568,8 +7967,8 @@
 TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithKeys) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
     window->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
 
@@ -6578,14 +7977,14 @@
     ASSERT_NO_FATAL_FAILURE(window->consumeFocusEvent(true));
 
     mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build());
-    ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ADISPLAY_ID_DEFAULT));
+    ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
     ASSERT_NO_FATAL_FAILURE(
             mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {gui::Uid{101}}));
 
     // The UP actions are not treated as device interaction.
     mDispatcher->notifyKey(KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build());
-    ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ADISPLAY_ID_DEFAULT));
+    ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
 }
@@ -6594,13 +7993,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
-                                                           ADISPLAY_ID_DEFAULT);
+                                                           ui::LogicalDisplayId::DEFAULT);
     left->setFrame(Rect(0, 0, 100, 100));
-    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     right->setFrame(Rect(100, 0, 200, 100));
-    sp<FakeWindowHandle> spy =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window",
+                                                          ui::LogicalDisplayId::DEFAULT);
     spy->setFrame(Rect(0, 0, 200, 100));
     spy->setTrustedOverlay(true);
     spy->setSpy(true);
@@ -6609,36 +8009,91 @@
             {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
 
     // Send hover move to the left window, and ensure hover enter is synthesized with a new eventId.
-    NotifyMotionArgs notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
-                                                     ADISPLAY_ID_DEFAULT, {PointF{50, 50}});
+    NotifyMotionArgs notifyArgs =
+            generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                               ui::LogicalDisplayId::DEFAULT, {PointF{50, 50}});
     mDispatcher->notifyMotion(notifyArgs);
 
-    const MotionEvent& leftEnter = left->consumeMotionEvent(
+    std::unique_ptr<MotionEvent> leftEnter = left->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), Not(WithEventId(notifyArgs.id)),
                   WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
-
+    ASSERT_NE(nullptr, leftEnter);
     spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                   Not(WithEventId(notifyArgs.id)),
-                                  Not(WithEventId(leftEnter.getId())),
+                                  Not(WithEventId(leftEnter->getId())),
                                   WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
 
     // Send move to the right window, and ensure hover exit and enter are synthesized with new ids.
-    notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
-                                    {PointF{150, 50}});
+    notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                                    ui::LogicalDisplayId::DEFAULT, {PointF{150, 50}});
     mDispatcher->notifyMotion(notifyArgs);
 
-    const MotionEvent& leftExit = left->consumeMotionEvent(
+    std::unique_ptr<MotionEvent> leftExit = left->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_EXIT), Not(WithEventId(notifyArgs.id)),
                   WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
-
+    ASSERT_NE(nullptr, leftExit);
     right->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                     Not(WithEventId(notifyArgs.id)),
-                                    Not(WithEventId(leftExit.getId())),
+                                    Not(WithEventId(leftExit->getId())),
                                     WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
 
     spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id)));
 }
 
+/**
+ * When a device reports a DOWN event, which lands in a window that supports splits, and then the
+ * device then reports a POINTER_DOWN, which lands in the location of a non-existing window, then
+ * the previous window should receive this event and not be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SingleDevicePointerDownEventRetentionWithoutWindowTarget) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_1_DOWN)));
+}
+
+/**
+ * When deviceA reports a DOWN event, which lands in a window that supports splits, and then deviceB
+ * also reports a DOWN event, which lands in the location of a non-existing window, then the
+ * previous window should receive deviceB's event and it should be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SecondDeviceDownEventDroppedWithoutWindowTarget) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+                                      .deviceId(deviceB)
+                                      .build());
+    window->assertNoEvents();
+}
+
 class InputDispatcherFallbackKeyTest : public InputDispatcherTest {
 protected:
     std::shared_ptr<FakeApplicationHandle> mApp;
@@ -6649,7 +8104,8 @@
 
         mApp = std::make_shared<FakeApplicationHandle>();
 
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Window",
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFrame(Rect(0, 0, 100, 100));
 
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
@@ -6664,8 +8120,8 @@
     }
 
     void consumeKey(bool handled, const ::testing::Matcher<KeyEvent>& matcher) {
-        KeyEvent* event = mWindow->consumeKey(handled);
-        ASSERT_NE(event, nullptr) << "Did not receive key event";
+        std::unique_ptr<KeyEvent> event = mWindow->consumeKey(handled);
+        ASSERT_NE(nullptr, event);
         ASSERT_THAT(*event, matcher);
     }
 };
@@ -6846,6 +8302,104 @@
     mWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherFallbackKeyTest, InputChannelRemovedDuringPolicyCall) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    mFakePolicy->setUnhandledKeyHandler([&](const KeyEvent& event) {
+        // When the unhandled key is reported to the policy next, remove the input channel.
+        mDispatcher->removeInputChannel(mWindow->getToken());
+        return KeyEventBuilder(event).keyCode(AKEYCODE_B).build();
+    });
+    // Release the original key, and let the app now handle the previously unhandled key.
+    // This should result in the previously generated fallback key to be cancelled.
+    // Since the policy was notified of the unhandled DOWN event earlier, it will also be notified
+    // of the UP event for consistency. The Dispatcher calls into the policy from its own thread
+    // without holding the lock, because it need to synchronously fetch the fallback key. While in
+    // the policy call, we will now remove the input channel. Once the policy call returns, the
+    // Dispatcher will no longer have a channel to send cancellation events to. Ensure this does
+    // not cause any crashes.
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, WindowRemovedDuringPolicyCall) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    mFakePolicy->setUnhandledKeyHandler([&](const KeyEvent& event) {
+        // When the unhandled key is reported to the policy next, remove the window.
+        mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
+        return KeyEventBuilder(event).keyCode(AKEYCODE_B).build();
+    });
+    // Release the original key, which the app will not handle. When this unhandled key is reported
+    // to the policy, the window will be removed.
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+
+    // Since the window was removed, it loses focus, and the channel state will be reset.
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED)));
+    mWindow->consumeFocusEvent(false);
+    mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, WindowRemovedWhileAwaitingFinishedSignal) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    const auto [seq, event] = mWindow->receiveEvent();
+    ASSERT_TRUE(seq.has_value() && event != nullptr) << "Failed to receive fallback event";
+    ASSERT_EQ(event->getType(), InputEventType::KEY);
+    ASSERT_THAT(static_cast<const KeyEvent&>(*event),
+                AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                      WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    // Remove the window now, which should generate a cancellations and make the window lose focus.
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A),
+                     WithFlags(AKEY_EVENT_FLAG_CANCELED)));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED)));
+    mWindow->consumeFocusEvent(false);
+
+    // Finish the event by reporting it as handled.
+    mWindow->finishEvent(*seq);
+    mWindow->assertNoEvents();
+}
+
 class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
 protected:
     static constexpr std::chrono::nanoseconds KEY_REPEAT_TIMEOUT = 40ms;
@@ -6855,18 +8409,16 @@
     sp<FakeWindowHandle> mWindow;
 
     virtual void SetUp() override {
-        mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
-        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
-        mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
-        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
-        ASSERT_EQ(OK, mDispatcher->start());
+        InputDispatcherTest::SetUp();
 
+        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
         setUpWindow();
     }
 
     void setUpWindow() {
         mApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Fake Window",
+                                             ui::LogicalDisplayId::DEFAULT);
 
         mWindow->setFocusable(true);
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
@@ -6875,13 +8427,14 @@
     }
 
     void sendAndConsumeKeyDown(int32_t deviceId) {
-        NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+        NotifyKeyArgs keyArgs =
+                generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT);
         keyArgs.deviceId = deviceId;
         keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Otherwise it won't generate repeat event
         mDispatcher->notifyKey(keyArgs);
 
         // Window should receive key down event.
-        mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+        mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     }
 
     void expectKeyRepeatOnce(int32_t repeatCount) {
@@ -6891,15 +8444,23 @@
     }
 
     void sendAndConsumeKeyUp(int32_t deviceId) {
-        NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+        NotifyKeyArgs keyArgs =
+                generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT);
         keyArgs.deviceId = deviceId;
         keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Unless it won't generate repeat event
         mDispatcher->notifyKey(keyArgs);
 
         // Window should receive key down event.
-        mWindow->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
+        mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT,
                               /*expectedFlags=*/0);
     }
+
+    void injectKeyRepeat(int32_t repeatCount) {
+        ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+                  injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, repeatCount,
+                            ui::LogicalDisplayId::DEFAULT))
+                << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
+    }
 };
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeat) {
@@ -6958,7 +8519,7 @@
     sendAndConsumeKeyDown(DEVICE_ID);
     expectKeyRepeatOnce(/*repeatCount=*/1);
     mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
-    mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT,
                           AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS);
     mWindow->assertNoEvents();
 }
@@ -6967,8 +8528,8 @@
     GTEST_SKIP() << "Flaky test (b/270393106)";
     sendAndConsumeKeyDown(/*deviceId=*/1);
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
-        KeyEvent* repeatEvent = mWindow->consumeKey();
-        ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
+        std::unique_ptr<KeyEvent> repeatEvent = mWindow->consumeKey();
+        ASSERT_NE(nullptr, repeatEvent);
         EXPECT_EQ(IdGenerator::Source::INPUT_DISPATCHER,
                   IdGenerator::getSource(repeatEvent->getId()));
     }
@@ -6980,14 +8541,25 @@
 
     std::unordered_set<int32_t> idSet;
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
-        KeyEvent* repeatEvent = mWindow->consumeKey();
-        ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
+        std::unique_ptr<KeyEvent> repeatEvent = mWindow->consumeKey();
+        ASSERT_NE(nullptr, repeatEvent);
         int32_t id = repeatEvent->getId();
         EXPECT_EQ(idSet.end(), idSet.find(id));
         idSet.insert(id);
     }
 }
 
+TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_CorrectRepeatCountWhenInjectKeyRepeat) {
+    injectKeyRepeat(0);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
+    for (int32_t repeatCount = 1; repeatCount <= 2; ++repeatCount) {
+        expectKeyRepeatOnce(repeatCount);
+    }
+    injectKeyRepeat(1);
+    // Expect repeatCount to be 3 instead of 1
+    expectKeyRepeatOnce(3);
+}
+
 /* Test InputDispatcher for MultiDisplay */
 class InputDispatcherFocusOnTwoDisplaysTest : public InputDispatcherTest {
 public:
@@ -6995,11 +8567,11 @@
         InputDispatcherTest::SetUp();
 
         application1 = std::make_shared<FakeApplicationHandle>();
-        windowInPrimary =
-                sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1", ADISPLAY_ID_DEFAULT);
+        windowInPrimary = sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1",
+                                                     ui::LogicalDisplayId::DEFAULT);
 
         // Set focus window for primary display, but focused display would be second one.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application1);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application1);
         windowInPrimary->setFocusable(true);
         mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
 
@@ -7040,9 +8612,10 @@
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayTouch) {
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     windowInSecondary->assertNoEvents();
 
     // Test touch down on second display.
@@ -7056,23 +8629,22 @@
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) {
     // Test inject a key down with display id specified.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDownNoRepeat(*mDispatcher, ui::LogicalDisplayId::DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    windowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     windowInSecondary->assertNoEvents();
 
     // Test inject a key down without display id specified.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
-    windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE);
+    windowInSecondary->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 
     // Remove all windows in secondary display.
     mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
 
     // Old focus should receive a cancel event.
-    windowInSecondary->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE,
-                                    AKEY_EVENT_FLAG_CANCELED);
+    windowInSecondary->consumeKeyUp(ui::LogicalDisplayId::INVALID, AKEY_EVENT_FLAG_CANCELED);
 
     // Test inject a key down, should timeout because of no target window.
     ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
@@ -7084,16 +8656,17 @@
 // Test per-display input monitors for motion event.
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) {
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
             FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    monitorInPrimary.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     windowInSecondary->assertNoEvents();
     monitorInSecondary.assertNoEvents();
 
@@ -7117,19 +8690,20 @@
     // If specific a display, it will dispatch to the focused window of particular display,
     // or it will dispatch to the focused window of focused display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_NONE))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL,
+                               ui::LogicalDisplayId::INVALID))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
-    windowInSecondary->consumeMotionDown(ADISPLAY_ID_NONE);
-    monitorInSecondary.consumeMotionDown(ADISPLAY_ID_NONE);
+    windowInSecondary->consumeMotionDown(ui::LogicalDisplayId::INVALID);
+    monitorInSecondary.consumeMotionDown(ui::LogicalDisplayId::INVALID);
 }
 
 // Test per-display input monitors for key event.
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) {
     // Input monitor per display.
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
             FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
@@ -7138,13 +8712,14 @@
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
-    windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE);
-    monitorInSecondary.consumeKeyDown(ADISPLAY_ID_NONE);
+    windowInSecondary->consumeKeyDown(ui::LogicalDisplayId::INVALID);
+    monitorInSecondary.consumeKeyDown(ui::LogicalDisplayId::INVALID);
 }
 
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CanFocusWindowOnUnfocusedDisplay) {
     sp<FakeWindowHandle> secondWindowInPrimary =
-            sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1_W2",
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindowInPrimary->setFocusable(true);
     mDispatcher->onWindowInfosChanged(
             {{*windowInPrimary->getInfo(), *secondWindowInPrimary->getInfo(),
@@ -7158,25 +8733,26 @@
 
     // Test inject a key down.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     windowInSecondary->assertNoEvents();
-    secondWindowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    secondWindowInPrimary->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) {
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
             FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    monitorInPrimary.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // Test touch down on second display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -7187,15 +8763,15 @@
 
     // Trigger cancel touch.
     mDispatcher->cancelCurrentTouch();
-    windowInPrimary->consumeMotionCancel(ADISPLAY_ID_DEFAULT);
-    monitorInPrimary.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT);
+    monitorInPrimary.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT);
     windowInSecondary->consumeMotionCancel(SECOND_DISPLAY_ID);
     monitorInSecondary.consumeMotionCancel(SECOND_DISPLAY_ID);
 
     // Test inject a move motion event, no window/monitor should receive the event.
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::FAILED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
@@ -7208,9 +8784,97 @@
     monitorInSecondary.assertNoEvents();
 }
 
+/**
+ * Send a key to the primary display and to the secondary display.
+ * Then cause the key on the primary display to be canceled by sending in a stale key.
+ * Ensure that the key on the primary display is canceled, and that the key on the secondary display
+ * does not get canceled.
+ */
+TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropKeyEvent_OnlyCancelCorrespondingKeyGesture) {
+    // Send a key down on primary display
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
+                    .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+                    .build());
+    windowInPrimary->consumeKeyEvent(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                           WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+    windowInSecondary->assertNoEvents();
+
+    // Send a key down on second display
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                    .displayId(SECOND_DISPLAY_ID)
+                    .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+                    .build());
+    windowInSecondary->consumeKeyEvent(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithDisplayId(SECOND_DISPLAY_ID)));
+    windowInPrimary->assertNoEvents();
+
+    // Send a valid key up event on primary display that will be dropped because it is stale
+    NotifyKeyArgs staleKeyUp =
+            KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
+                    .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+                    .build();
+    static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms;
+    mFakePolicy->setStaleEventTimeout(STALE_EVENT_TIMEOUT);
+    std::this_thread::sleep_for(STALE_EVENT_TIMEOUT);
+    mDispatcher->notifyKey(staleKeyUp);
+
+    // Only the key gesture corresponding to the dropped event should receive the cancel event.
+    // Therefore, windowInPrimary should get the cancel event and windowInSecondary should not
+    // receive any events.
+    windowInPrimary->consumeKeyEvent(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                           WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                           WithFlags(AKEY_EVENT_FLAG_CANCELED)));
+    windowInSecondary->assertNoEvents();
+}
+
+/**
+ * Similar to 'WhenDropKeyEvent_OnlyCancelCorrespondingKeyGesture' but for motion events.
+ */
+TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropMotionEvent_OnlyCancelCorrespondingGesture) {
+    // Send touch down on primary display.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
+                    .build());
+    windowInPrimary->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+    windowInSecondary->assertNoEvents();
+
+    // Send touch down on second display.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .displayId(SECOND_DISPLAY_ID)
+                    .build());
+    windowInPrimary->assertNoEvents();
+    windowInSecondary->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(SECOND_DISPLAY_ID)));
+
+    // inject a valid MotionEvent on primary display that will be stale when it arrives.
+    NotifyMotionArgs staleMotionUp =
+            MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .build();
+    static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms;
+    mFakePolicy->setStaleEventTimeout(STALE_EVENT_TIMEOUT);
+    std::this_thread::sleep_for(STALE_EVENT_TIMEOUT);
+    mDispatcher->notifyMotion(staleMotionUp);
+
+    // For stale motion events, we let the gesture to complete. This behaviour is different from key
+    // events, where we would cancel the current keys instead.
+    windowInPrimary->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    windowInSecondary->assertNoEvents();
+}
+
 class InputFilterTest : public InputDispatcherTest {
 protected:
-    void testNotifyMotion(int32_t displayId, bool expectToBeFiltered,
+    void testNotifyMotion(ui::LogicalDisplayId displayId, bool expectToBeFiltered,
                           const ui::Transform& transform = ui::Transform()) {
         NotifyMotionArgs motionArgs;
 
@@ -7249,19 +8913,19 @@
 // Test InputFilter for MotionEvent
 TEST_F(InputFilterTest, MotionEvent_InputFilter) {
     // Since the InputFilter is disabled by default, check if touch events aren't filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false);
+    testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/false);
     testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false);
 
     // Enable InputFilter
     mDispatcher->setInputFilterEnabled(true);
     // Test touch on both primary and second display, and check if both events are filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true);
+    testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/true);
     testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true);
 
     // Disable InputFilter
     mDispatcher->setInputFilterEnabled(false);
     // Test touch on both primary and second display, and check if both events aren't filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false);
+    testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/false);
     testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false);
 }
 
@@ -7290,7 +8954,7 @@
     secondDisplayTransform.set({-6.6, -5.5, -4.4, -3.3, -2.2, -1.1, 0, 0, 1});
 
     std::vector<gui::DisplayInfo> displayInfos(2);
-    displayInfos[0].displayId = ADISPLAY_ID_DEFAULT;
+    displayInfos[0].displayId = ui::LogicalDisplayId::DEFAULT;
     displayInfos[0].transform = firstDisplayTransform;
     displayInfos[1].displayId = SECOND_DISPLAY_ID;
     displayInfos[1].transform = secondDisplayTransform;
@@ -7301,7 +8965,8 @@
     mDispatcher->setInputFilterEnabled(true);
 
     // Ensure the correct transforms are used for the displays.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true, firstDisplayTransform);
+    testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/true,
+                     firstDisplayTransform);
     testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true, secondDisplayTransform);
 }
 
@@ -7320,9 +8985,9 @@
         std::shared_ptr<InputApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
         mWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Test Window",
-                                             ADISPLAY_ID_DEFAULT);
+                                             ui::LogicalDisplayId::DEFAULT);
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
         mWindow->setFocusable(true);
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mWindow);
@@ -7335,8 +9000,8 @@
 
         const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
         event.initialize(InputEvent::nextId(), injectedDeviceId, AINPUT_SOURCE_KEYBOARD,
-                         ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A,
-                         KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime);
+                         ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0,
+                         AKEYCODE_A, KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime);
         const int32_t additionalPolicyFlags =
                 POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT;
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -7407,22 +9072,155 @@
                     /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0);
 }
 
+class InputDispatcherUserActivityPokeTests : public InputDispatcherTest {
+protected:
+    virtual void SetUp() override {
+        InputDispatcherTest::SetUp();
+
+        std::shared_ptr<FakeApplicationHandle> application =
+                std::make_shared<FakeApplicationHandle>();
+        application->setDispatchingTimeout(100ms);
+        mWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
+        mWindow->setFrame(Rect(0, 0, 100, 100));
+        mWindow->setDispatchingTimeout(100ms);
+        mWindow->setFocusable(true);
+
+        // Set focused application.
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
+
+        mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
+        setFocusedWindow(mWindow);
+        mWindow->consumeFocusEvent(true);
+    }
+
+    void notifyAndConsumeMotion(int32_t action, uint32_t source, ui::LogicalDisplayId displayId,
+                                nsecs_t eventTime) {
+        mDispatcher->notifyMotion(MotionArgsBuilder(action, source)
+                                          .displayId(displayId)
+                                          .eventTime(eventTime)
+                                          .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                          .build());
+        mWindow->consumeMotionEvent(WithMotionAction(action));
+    }
+
+private:
+    sp<FakeWindowHandle> mWindow;
+};
+
+TEST_F_WITH_FLAGS(
+        InputDispatcherUserActivityPokeTests, MinPokeTimeObserved,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            rate_limit_user_activity_poke_in_dispatcher))) {
+    mDispatcher->setMinTimeBetweenUserActivityPokes(50ms);
+
+    // First event of type TOUCH. Should poke.
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           milliseconds_to_nanoseconds(50));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
+
+    // 80ns > 50ns has passed since previous TOUCH event. Should poke.
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           milliseconds_to_nanoseconds(130));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
+
+    // First event of type OTHER. Should poke (despite being within 50ns of previous TOUCH event).
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
+                           ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(135));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER,
+              ui::LogicalDisplayId::DEFAULT}});
+
+    // Within 50ns of previous TOUCH event. Should NOT poke.
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           milliseconds_to_nanoseconds(140));
+    mFakePolicy->assertUserActivityNotPoked();
+
+    // Within 50ns of previous OTHER event. Should NOT poke.
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
+                           ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(150));
+    mFakePolicy->assertUserActivityNotPoked();
+
+    // Within 50ns of previous TOUCH event (which was at time 130). Should NOT poke.
+    // Note that STYLUS is mapped to TOUCH user activity, since it's a pointer-type source.
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT,
+                           milliseconds_to_nanoseconds(160));
+    mFakePolicy->assertUserActivityNotPoked();
+
+    // 65ns > 50ns has passed since previous OTHER event. Should poke.
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
+                           ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(200));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER,
+              ui::LogicalDisplayId::DEFAULT}});
+
+    // 170ns > 50ns has passed since previous TOUCH event. Should poke.
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT,
+                           milliseconds_to_nanoseconds(300));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
+
+    // Assert that there's no more user activity poke event.
+    mFakePolicy->assertUserActivityNotPoked();
+}
+
+TEST_F_WITH_FLAGS(
+        InputDispatcherUserActivityPokeTests, DefaultMinPokeTimeOf100MsUsed,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            rate_limit_user_activity_poke_in_dispatcher))) {
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           milliseconds_to_nanoseconds(200));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
+
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           milliseconds_to_nanoseconds(280));
+    mFakePolicy->assertUserActivityNotPoked();
+
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           milliseconds_to_nanoseconds(340));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
+}
+
+TEST_F_WITH_FLAGS(
+        InputDispatcherUserActivityPokeTests, ZeroMinPokeTimeDisablesRateLimiting,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            rate_limit_user_activity_poke_in_dispatcher))) {
+    mDispatcher->setMinTimeBetweenUserActivityPokes(0ms);
+
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           20);
+    mFakePolicy->assertUserActivityPoked();
+
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           30);
+    mFakePolicy->assertUserActivityPoked();
+}
+
 class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest {
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
 
         std::shared_ptr<FakeApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
-        mUnfocusedWindow =
-                sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+        mUnfocusedWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                      ui::LogicalDisplayId::DEFAULT);
         mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
 
-        mFocusedWindow =
-                sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+        mFocusedWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                                    ui::LogicalDisplayId::DEFAULT);
         mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
 
         // Set focused application.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
         mFocusedWindow->setFocusable(true);
 
         // Expect one focus window exist in display.
@@ -7450,8 +9248,8 @@
 // the onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_Success) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {20, 20}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {20, 20}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mUnfocusedWindow->consumeMotionDown();
 
@@ -7464,7 +9262,7 @@
 // onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonPointerSource) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ui::LogicalDisplayId::DEFAULT,
                                {20, 20}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeMotionDown();
@@ -7477,9 +9275,9 @@
 // have focus. Ensure no window received the onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMotionFailure) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDownNoRepeat(*mDispatcher, ui::LogicalDisplayId::DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mFocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mFocusedWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertOnPointerDownWasNotCalled();
@@ -7490,8 +9288,8 @@
 // onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_TOUCH_POINT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_TOUCH_POINT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeMotionDown();
 
@@ -7510,7 +9308,8 @@
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, event))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mUnfocusedWindow->consumeAnyMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mUnfocusedWindow->consumeAnyMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertOnPointerDownWasNotCalled();
@@ -7527,11 +9326,10 @@
         std::shared_ptr<FakeApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
         mWindow1 = sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window 1",
-                                              ADISPLAY_ID_DEFAULT);
+                                              ui::LogicalDisplayId::DEFAULT);
         mWindow1->setFrame(Rect(0, 0, 100, 100));
 
-        mWindow2 = sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window 2",
-                                              ADISPLAY_ID_DEFAULT, mWindow1->getToken());
+        mWindow2 = mWindow1->clone(ui::LogicalDisplayId::DEFAULT);
         mWindow2->setFrame(Rect(100, 100, 200, 200));
 
         mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
@@ -7550,12 +9348,9 @@
     void consumeMotionEvent(const sp<FakeWindowHandle>& window, int32_t expectedAction,
                             const std::vector<PointF>& points) {
         const std::string name = window->getName();
-        MotionEvent* motionEvent = window->consumeMotion();
-
-        ASSERT_NE(nullptr, motionEvent)
-                << name.c_str() << ": consumer should have returned non-NULL event.";
-
-        ASSERT_THAT(*motionEvent, WithMotionAction(expectedAction));
+        std::unique_ptr<MotionEvent> motionEvent =
+                window->consumeMotionEvent(WithMotionAction(expectedAction));
+        ASSERT_NE(nullptr, motionEvent);
         ASSERT_EQ(points.size(), motionEvent->getPointerCount());
 
         for (size_t i = 0; i < points.size(); i++) {
@@ -7571,13 +9366,13 @@
         }
     }
 
-    void touchAndAssertPositions(int32_t action, const std::vector<PointF>& touchedPoints,
+    void touchAndAssertPositions(sp<FakeWindowHandle> touchedWindow, int32_t action,
+                                 const std::vector<PointF>& touchedPoints,
                                  std::vector<PointF> expectedPoints) {
         mDispatcher->notifyMotion(generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN,
-                                                     ADISPLAY_ID_DEFAULT, touchedPoints));
+                                                     ui::LogicalDisplayId::DEFAULT, touchedPoints));
 
-        // Always consume from window1 since it's the window that has the InputReceiver
-        consumeMotionEvent(mWindow1, action, expectedPoints);
+        consumeMotionEvent(touchedWindow, action, expectedPoints);
     }
 };
 
@@ -7585,15 +9380,15 @@
     // Touch Window 1
     PointF touchedPoint = {10, 10};
     PointF expectedPoint = getPointInWindow(mWindow1->getInfo(), touchedPoint);
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
 
     // Release touch on Window 1
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint});
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint});
 
     // Touch Window 2
     touchedPoint = {150, 150};
     expectedPoint = getPointInWindow(mWindow2->getInfo(), touchedPoint);
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
+    touchAndAssertPositions(mWindow2, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
 }
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchDifferentTransform) {
@@ -7604,21 +9399,21 @@
     // Touch Window 1
     PointF touchedPoint = {10, 10};
     PointF expectedPoint = getPointInWindow(mWindow1->getInfo(), touchedPoint);
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
     // Release touch on Window 1
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint});
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint});
 
     // Touch Window 2
     touchedPoint = {150, 150};
     expectedPoint = getPointInWindow(mWindow2->getInfo(), touchedPoint);
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint});
+    touchAndAssertPositions(mWindow2, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
+    touchAndAssertPositions(mWindow2, AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint});
 
     // Update the transform so rotation is set
     mWindow2->setWindowTransform(0, -1, 1, 0);
     mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
     expectedPoint = getPointInWindow(mWindow2->getInfo(), touchedPoint);
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
+    touchAndAssertPositions(mWindow2, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
 }
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchDifferentTransform) {
@@ -7628,22 +9423,25 @@
     // Touch Window 1
     std::vector<PointF> touchedPoints = {PointF{10, 10}};
     std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])};
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
+    // Since this is part of the same touch gesture that has already been dispatched to Window 1,
+    // the touch stream from Window 2 will be merged with the stream in Window 1. The merged stream
+    // will continue to be dispatched through Window 1.
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Release Window 2
-    touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, POINTER_1_UP, touchedPoints, expectedPoints);
     expectedPoints.pop_back();
 
     // Update the transform so rotation is set for Window 2
     mWindow2->setWindowTransform(0, -1, 1, 0);
     mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints);
 }
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTransform) {
@@ -7653,37 +9451,37 @@
     // Touch Window 1
     std::vector<PointF> touchedPoints = {PointF{10, 10}};
     std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])};
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
 
-    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
     expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0]),
                       getPointInWindow(mWindow2->getInfo(), touchedPoints[1])};
 
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
 
     // Release Window 2
-    touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, POINTER_1_UP, touchedPoints, expectedPoints);
     expectedPoints.pop_back();
 
     // Touch Window 2
     mWindow2->setWindowTransform(0, -1, 1, 0);
     mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
     expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0]),
                       getPointInWindow(mWindow2->getInfo(), touchedPoints[1])};
 
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
 }
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithScale) {
@@ -7693,20 +9491,20 @@
     // Touch Window 1
     std::vector<PointF> touchedPoints = {PointF{10, 10}};
     std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])};
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
 
-    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
     expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0]),
                       getPointInWindow(mWindow2->getInfo(), touchedPoints[1])};
 
-    touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
+    touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
 }
 
 /**
@@ -7719,14 +9517,14 @@
 
     // Touch down in window 1
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {{50, 50}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {{50, 50}}));
     consumeMotionEvent(mWindow1, ACTION_DOWN, {{50, 50}});
 
     // Move touch to be above window 2. Even though window 1 is slippery, touch should not slip.
     // That means the gesture should continue normally, without any ACTION_CANCEL or ACTION_DOWN
     // getting generated.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {{150, 150}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {{150, 150}}));
 
     consumeMotionEvent(mWindow1, ACTION_MOVE, {{150, 150}});
 }
@@ -7750,7 +9548,7 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150))
                                       .build());
     consumeMotionEvent(mWindow1, ACTION_HOVER_EXIT, {{50, 50}});
-    consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER,
+    consumeMotionEvent(mWindow2, ACTION_HOVER_ENTER,
                        {getPointInWindow(mWindow2->getInfo(), PointF{150, 150})});
 }
 
@@ -7761,13 +9559,13 @@
         mApplication = std::make_shared<FakeApplicationHandle>();
         mApplication->setDispatchingTimeout(100ms);
         mWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "TestWindow",
-                                             ADISPLAY_ID_DEFAULT);
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFrame(Rect(0, 0, 30, 30));
         mWindow->setDispatchingTimeout(100ms);
         mWindow->setFocusable(true);
 
         // Set focused application.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication);
 
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mWindow);
@@ -7798,8 +9596,8 @@
     }
 
     sp<FakeWindowHandle> addSpyWindow() {
-        sp<FakeWindowHandle> spy =
-                sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Spy",
+                                                              ui::LogicalDisplayId::DEFAULT);
         spy->setTrustedOverlay(true);
         spy->setFocusable(false);
         spy->setSpy(true);
@@ -7821,7 +9619,7 @@
 // Send a regular key and respond, which should not cause an ANR.
 TEST_F(InputDispatcherSingleWindowAnr, WhenKeyIsConsumed_NoAnr) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher));
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
 }
@@ -7832,15 +9630,16 @@
     mWindow->consumeFocusEvent(false);
 
     InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, CONSUME_TIMEOUT_EVENT_EXPECTED,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                      CONSUME_TIMEOUT_EVENT_EXPECTED,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
     // Key will not go to window because we have no focused window.
     // The 'no focused window' ANR timer should start instead.
 
     // Now, the focused application goes away.
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, nullptr);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, nullptr);
     // The key should get dropped and there should be no ANR.
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
@@ -7852,17 +9651,17 @@
 // So InputDispatcher will enqueue ACTION_CANCEL event as well.
 TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
 
-    std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
+    const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(sequenceNum);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
 
     mWindow->finishEvent(*sequenceNum);
     mWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
 }
@@ -7871,7 +9670,7 @@
 TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) {
     // Inject a key, and don't respond - expect that ANR is called.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher));
-    std::optional<uint32_t> sequenceNum = mWindow->receiveEvent();
+    const auto [sequenceNum, _] = mWindow->receiveEvent();
     ASSERT_TRUE(sequenceNum);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
@@ -7886,8 +9685,8 @@
 
     // taps on the window work as normal
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -7896,8 +9695,9 @@
     // We specify the injection timeout to be smaller than the application timeout, to ensure that
     // injection times out (instead of failing).
     const InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 50ms, /*allowKeyRepeat=*/false);
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, 50ms,
+                      /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
     const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication);
@@ -7920,11 +9720,13 @@
             std::chrono::nanoseconds(STALE_EVENT_TIMEOUT).count();
 
     // Define a valid key down event that is stale (too old).
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /*flags=*/0, AKEYCODE_A, KEY_A,
-                     AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime);
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN,
+                     /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime,
+                     eventTime);
 
-    const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
+    const int32_t policyFlags =
+            POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT;
 
     InputEventInjectionResult result =
             mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
@@ -7943,7 +9745,7 @@
 TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) {
     const std::chrono::duration appTimeout = 400ms;
     mApplication->setDispatchingTimeout(appTimeout);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication);
 
     mWindow->setFocusable(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
@@ -7955,8 +9757,9 @@
     const std::chrono::duration eventInjectionTimeout = 100ms;
     ASSERT_LT(eventInjectionTimeout, appTimeout);
     const InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, eventInjectionTimeout,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT,
+                      eventInjectionTimeout,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result)
             << "result=" << ftl::enum_string(result);
@@ -8004,20 +9807,20 @@
 TEST_F(InputDispatcherSingleWindowAnr, Anr_HandlesEventsWithIdenticalTimestamps) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                      ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
+                      ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION,
                       {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                        AMOTION_EVENT_INVALID_CURSOR_POSITION},
                       500ms, InputEventInjectionSync::WAIT_FOR_RESULT, currentTime);
 
     // Now send ACTION_UP, with identical timestamp
     injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                      ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
+                      ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION,
                       {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                        AMOTION_EVENT_INVALID_CURSOR_POSITION},
                       500ms, InputEventInjectionSync::WAIT_FOR_RESULT, currentTime);
 
     // We have now sent down and up. Let's consume first event and then ANR on the second.
-    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
 }
@@ -8027,18 +9830,18 @@
     sp<FakeWindowHandle> spy = addSpyWindow();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
     mWindow->consumeMotionDown();
 
-    std::optional<uint32_t> sequenceNum = spy->receiveEvent(); // ACTION_DOWN
+    const auto [sequenceNum, _] = spy->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(sequenceNum);
     const std::chrono::duration timeout = spy->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy);
 
     spy->finishEvent(*sequenceNum);
     spy->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid());
 }
@@ -8049,9 +9852,10 @@
     sp<FakeWindowHandle> spy = addSpyWindow();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT));
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher, ADISPLAY_ID_DEFAULT));
+              injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT));
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectKeyUp(*mDispatcher, ui::LogicalDisplayId::DEFAULT));
 
     // Stuck on the ACTION_UP
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -8059,10 +9863,10 @@
 
     // New tap will go to the spy window, but not to the window
     tapOnWindow();
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
-    mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT); // still the previous motion
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT); // still the previous motion
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mWindow->assertNoEvents();
@@ -8075,8 +9879,8 @@
     sp<FakeWindowHandle> spy = addSpyWindow();
 
     tapOnWindow();
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
     mWindow->consumeMotionDown();
     // Stuck on the ACTION_UP
@@ -8085,10 +9889,10 @@
 
     // New tap will go to the spy window, but not to the window
     tapOnWindow();
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
-    mWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); // still the previous motion
+    mWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); // still the previous motion
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mWindow->assertNoEvents();
@@ -8098,13 +9902,14 @@
 TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) {
     mDispatcher->setMonitorDispatchingTimeoutForTest(SPY_TIMEOUT);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
 
-    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     const std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
     ASSERT_TRUE(consumeSeq);
 
@@ -8112,7 +9917,7 @@
                                                          MONITOR_PID);
 
     monitor.finishEvent(*consumeSeq);
-    monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(monitor.getToken(), MONITOR_PID);
@@ -8151,8 +9956,8 @@
 // it.
 TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
 
     const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(windowTimeout, mWindow);
@@ -8162,7 +9967,7 @@
     // When the ANR happened, dispatcher should abort the current event stream via ACTION_CANCEL
     mWindow->consumeMotionDown();
     mWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     mWindow->assertNoEvents();
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
@@ -8172,44 +9977,34 @@
 /**
  * If a window is processing a motion event, and then a key event comes in, the key event should
  * not get delivered to the focused window until the motion is processed.
- *
- * Warning!!!
- * This test depends on the value of android::inputdispatcher::KEY_WAITING_FOR_MOTION_TIMEOUT
- * and the injection timeout that we specify when injecting the key.
- * We must have the injection timeout (100ms) be smaller than
- *  KEY_WAITING_FOR_MOTION_TIMEOUT (currently 500ms).
- *
- * If that value changes, this test should also change.
  */
 TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) {
+    // The timeouts in this test are established by relying on the fact that the "key waiting for
+    // events timeout" is equal to 500ms.
+    ASSERT_EQ(mFakePolicy->getKeyWaitingForEventsTimeout(), 500ms);
     mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
 
     tapOnWindow();
-    std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent();
+    const auto& [downSequenceNum, downEvent] = mWindow->receiveEvent();
     ASSERT_TRUE(downSequenceNum);
-    std::optional<uint32_t> upSequenceNum = mWindow->receiveEvent();
+    const auto& [upSequenceNum, upEvent] = mWindow->receiveEvent();
     ASSERT_TRUE(upSequenceNum);
-    // Don't finish the events yet, and send a key
-    // Injection will "succeed" because we will eventually give up and send the key to the focused
-    // window even if motions are still being processed. But because the injection timeout is short,
-    // we will receive INJECTION_TIMED_OUT as the result.
 
-    InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 100ms);
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
+    // Don't finish the events yet, and send a key
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                    .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+                    .build());
     // Key will not be sent to the window, yet, because the window is still processing events
     // and the key remains pending, waiting for the touch events to be processed
     // Make sure that `assertNoEvents` doesn't wait too long, because it could cause an ANR.
-    // Rely here on the fact that it uses CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood.
-    static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms);
-    mWindow->assertNoEvents();
+    mWindow->assertNoEvents(100ms);
 
-    std::this_thread::sleep_for(500ms);
+    std::this_thread::sleep_for(400ms);
     // if we wait long enough though, dispatcher will give up, and still send the key
     // to the focused window, even though we have not yet finished the motion event
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     mWindow->finishEvent(*downSequenceNum);
     mWindow->finishEvent(*upSequenceNum);
 }
@@ -8221,14 +10016,17 @@
  * focused window right away.
  */
 TEST_F(InputDispatcherSingleWindowAnr,
-       PendingKey_IsDroppedWhileMotionIsProcessedAndNewTouchComesIn) {
+       PendingKey_IsDeliveredWhileMotionIsProcessingAndNewTouchComesIn) {
+    // The timeouts in this test are established by relying on the fact that the "key waiting for
+    // events timeout" is equal to 500ms.
+    ASSERT_EQ(mFakePolicy->getKeyWaitingForEventsTimeout(), 500ms);
     mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
 
     tapOnWindow();
-    std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent();
+    const auto& [downSequenceNum, _] = mWindow->receiveEvent();
     ASSERT_TRUE(downSequenceNum);
-    std::optional<uint32_t> upSequenceNum = mWindow->receiveEvent();
+    const auto& [upSequenceNum, upEvent] = mWindow->receiveEvent();
     ASSERT_TRUE(upSequenceNum);
     // Don't finish the events yet, and send a key
     mDispatcher->notifyKey(
@@ -8236,15 +10034,19 @@
                     .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
                     .build());
     // At this point, key is still pending, and should not be sent to the application yet.
-    // Make sure the `assertNoEvents` check doesn't take too long. It uses
-    // CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood.
-    static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms);
-    mWindow->assertNoEvents();
+    mWindow->assertNoEvents(100ms);
 
     // Now tap down again. It should cause the pending key to go to the focused window right away.
     tapOnWindow();
-    mWindow->consumeKeyEvent(WithKeyAction(AKEY_EVENT_ACTION_DOWN)); // it doesn't matter that we
-    // haven't ack'd the other events yet. We can finish events in any order.
+    // Now that we tapped, we should receive the key immediately.
+    // Since there's still room for slowness, we use 200ms, which is much less than
+    // the "key waiting for events' timeout of 500ms minus the already waited 100ms duration.
+    std::unique_ptr<InputEvent> keyEvent = mWindow->consume(200ms);
+    ASSERT_NE(nullptr, keyEvent);
+    ASSERT_EQ(InputEventType::KEY, keyEvent->getType());
+    ASSERT_THAT(static_cast<KeyEvent&>(*keyEvent), WithKeyAction(AKEY_EVENT_ACTION_DOWN));
+    // it doesn't matter that we haven't ack'd the other events yet. We can finish events in any
+    // order.
     mWindow->finishEvent(*downSequenceNum); // first tap's ACTION_DOWN
     mWindow->finishEvent(*upSequenceNum);   // first tap's ACTION_UP
     mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
@@ -8264,7 +10066,7 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
                                       .build());
 
-    std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
+    const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(sequenceNum);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
@@ -8301,6 +10103,61 @@
     mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 }
 
+// Send an event to the app and have the app not respond right away. Then remove the app window.
+// When the window is removed, the dispatcher will cancel the events for that window.
+// So InputDispatcher will enqueue ACTION_CANCEL event as well.
+TEST_F(InputDispatcherSingleWindowAnr, AnrAfterWindowRemoval) {
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {WINDOW_LOCATION}));
+
+    const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
+    ASSERT_TRUE(sequenceNum);
+
+    // Remove the window, but the input channel should remain alive.
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
+
+    const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+    // Since the window was removed, Dispatcher does not know the PID associated with the window
+    // anymore, so the policy is notified without the PID.
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken(),
+                                                         /*pid=*/std::nullopt);
+
+    mWindow->finishEvent(*sequenceNum);
+    // The cancellation was generated when the window was removed, along with the focus event.
+    mWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+    mWindow->consumeFocusEvent(false);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), /*pid=*/std::nullopt);
+}
+
+// Send an event to the app and have the app not respond right away. Wait for the policy to be
+// notified of the unresponsive window, then remove the app window.
+TEST_F(InputDispatcherSingleWindowAnr, AnrFollowedByWindowRemoval) {
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {WINDOW_LOCATION}));
+
+    const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
+    ASSERT_TRUE(sequenceNum);
+    const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
+
+    // Remove the window, but the input channel should remain alive.
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
+
+    mWindow->finishEvent(*sequenceNum);
+    // The cancellation was generated during the ANR, and the window lost focus when it was removed.
+    mWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+    mWindow->consumeFocusEvent(false);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    // Since the window was removed, Dispatcher does not know the PID associated with the window
+    // becoming responsive, so the policy is notified without the PID.
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), /*pid=*/std::nullopt);
+}
+
 class InputDispatcherMultiWindowAnr : public InputDispatcherTest {
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
@@ -8308,18 +10165,18 @@
         mApplication = std::make_shared<FakeApplicationHandle>();
         mApplication->setDispatchingTimeout(100ms);
         mUnfocusedWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Unfocused",
-                                                      ADISPLAY_ID_DEFAULT);
+                                                      ui::LogicalDisplayId::DEFAULT);
         mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
         // Adding FLAG_WATCH_OUTSIDE_TOUCH to receive ACTION_OUTSIDE when another window is tapped
         mUnfocusedWindow->setWatchOutsideTouch(true);
 
         mFocusedWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Focused",
-                                                    ADISPLAY_ID_DEFAULT);
+                                                    ui::LogicalDisplayId::DEFAULT);
         mFocusedWindow->setDispatchingTimeout(100ms);
         mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
 
         // Set focused application.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication);
         mFocusedWindow->setFocusable(true);
 
         // Expect one focus window exist in display.
@@ -8351,11 +10208,11 @@
 private:
     void tap(const PointF& location) {
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                   location));
+                  injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, location));
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                 location));
+                  injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                                 ui::LogicalDisplayId::DEFAULT, location));
     }
 };
 
@@ -8380,8 +10237,7 @@
                                         .build()));
     mFocusedWindow->consumeMotionDown();
     mFocusedWindow->consumeMotionUp();
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -8394,7 +10250,7 @@
                                                          .x(FOCUSED_WINDOW_LOCATION.x)
                                                          .y(FOCUSED_WINDOW_LOCATION.y))
                                         .build()));
-    std::optional<uint32_t> unfocusedSequenceNum = mUnfocusedWindow->receiveEvent();
+    const auto [unfocusedSequenceNum, _] = mUnfocusedWindow->receiveEvent();
     ASSERT_TRUE(unfocusedSequenceNum);
 
     const std::chrono::duration timeout =
@@ -8457,12 +10313,11 @@
 // At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events.
 TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
     tapOnFocusedWindow();
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0);
     // Receive the events, but don't respond
-    std::optional<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN
+    const auto [downEventSequenceNum, downEvent] = mFocusedWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(downEventSequenceNum);
-    std::optional<uint32_t> upEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_UP
+    const auto [upEventSequenceNum, upEvent] = mFocusedWindow->receiveEvent(); // ACTION_UP
     ASSERT_TRUE(upEventSequenceNum);
     const std::chrono::duration timeout =
             mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -8471,10 +10326,10 @@
     // Tap once again
     // We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_LOCATION));
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              FOCUSED_WINDOW_LOCATION));
     // Unfocused window does not receive ACTION_OUTSIDE because the tapped window is not a
     // valid touch target
@@ -8495,8 +10350,8 @@
 // If you tap outside of all windows, there will not be ANR
 TEST_F(InputDispatcherMultiWindowAnr, TapOutsideAllWindows_DoesNotAnr) {
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               LOCATION_OUTSIDE_ALL_WINDOWS));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, LOCATION_OUTSIDE_ALL_WINDOWS));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
 }
@@ -8508,8 +10363,8 @@
             {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_LOCATION));
 
     std::this_thread::sleep_for(mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT));
     ASSERT_TRUE(mDispatcher->waitForIdle());
@@ -8540,17 +10395,18 @@
             {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
 
     tapOnUnfocusedWindow();
-    std::optional<uint32_t> downSequenceNum = mUnfocusedWindow->receiveEvent();
+    const auto [downSequenceNum, downEvent] = mUnfocusedWindow->receiveEvent();
     ASSERT_TRUE(downSequenceNum);
-    std::optional<uint32_t> upSequenceNum = mUnfocusedWindow->receiveEvent();
+    const auto [upSequenceNum, upEvent] = mUnfocusedWindow->receiveEvent();
     ASSERT_TRUE(upSequenceNum);
     // Don't finish the events yet, and send a key
     // Injection will succeed because we will eventually give up and send the key to the focused
     // window even if motions are still being processed.
 
     InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms);
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                      /*injectionTimeout=*/100ms);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
     // Key will not be sent to the window, yet, because the window is still processing events
     // and the key remains pending, waiting for the touch events to be processed.
@@ -8576,7 +10432,7 @@
 
     // Now that all queues are cleared and no backlog in the connections, the key event
     // can finally go to the newly focused "mUnfocusedWindow".
-    mUnfocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mUnfocusedWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     mFocusedWindow->assertNoEvents();
     mUnfocusedWindow->assertNoEvents();
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -8587,15 +10443,15 @@
 // The other window should not be affected by that.
 TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) {
     // Touch Window 1
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {FOCUSED_WINDOW_LOCATION}));
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mDispatcher->notifyMotion(
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {FOCUSED_WINDOW_LOCATION}));
+    mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0);
 
     // Touch Window 2
     mDispatcher->notifyMotion(
-            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT,
                                {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION}));
 
     const std::chrono::duration timeout =
@@ -8606,8 +10462,7 @@
     mFocusedWindow->consumeMotionDown();
     // Focused window may or may not receive ACTION_MOVE
     // But it should definitely receive ACTION_CANCEL due to the ANR
-    InputEvent* event;
-    std::optional<int32_t> moveOrCancelSequenceNum = mFocusedWindow->receiveEvent(&event);
+    const auto [moveOrCancelSequenceNum, event] = mFocusedWindow->receiveEvent();
     ASSERT_TRUE(moveOrCancelSequenceNum);
     mFocusedWindow->finishEvent(*moveOrCancelSequenceNum);
     ASSERT_NE(nullptr, event);
@@ -8642,7 +10497,7 @@
     std::shared_ptr<FakeApplicationHandle> focusedApplication =
             std::make_shared<FakeApplicationHandle>();
     focusedApplication->setDispatchingTimeout(300ms);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, focusedApplication);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, focusedApplication);
     // The application that owns 'mFocusedWindow' and 'mUnfocusedWindow' is not focused.
     mFocusedWindow->setFocusable(false);
 
@@ -8654,8 +10509,9 @@
     // 'focusedApplication' will get blamed if this timer completes.
     // Key will not be sent anywhere because we have no focused window. It will remain pending.
     InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                      /*injectionTimeout=*/100ms,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
 
@@ -8669,9 +10525,9 @@
     std::this_thread::sleep_for(100ms);
 
     // Touch unfocused window. This should force the pending key to get dropped.
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {UNFOCUSED_WINDOW_LOCATION}));
+    mDispatcher->notifyMotion(
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {UNFOCUSED_WINDOW_LOCATION}));
 
     // We do not consume the motion right away, because that would require dispatcher to first
     // process (== drop) the key event, and by that time, ANR will be raised.
@@ -8691,6 +10547,106 @@
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyAnrWasNotCalled());
 }
 
+/**
+ * If we are pruning input queue, we should never drop pointer events. Otherwise, we risk having
+ * an inconsistent event stream inside the dispatcher. In this test, we make sure that the
+ * dispatcher doesn't prune pointer events incorrectly.
+ *
+ * This test reproduces a crash in InputDispatcher.
+ * To reproduce the crash, we need to simulate the conditions for "pruning input queue" to occur.
+ *
+ * Keep the currently focused application (mApplication), and have no focused window.
+ * We set up two additional windows:
+ * 1) The navigation bar window. This simulates the system "NavigationBar", which is used in the
+ * 3-button navigation mode. This window injects a BACK button when it's touched. 2) The application
+ * window. This window is not focusable, but is touchable.
+ *
+ * We first touch the navigation bar, which causes it to inject a key. Since there's no focused
+ * window, the dispatcher doesn't process this key, and all other events inside dispatcher are now
+ * blocked. The dispatcher is waiting for 'mApplication' to add a focused window.
+ *
+ * Now, we touch "Another window". This window is owned by a different application than
+ * 'mApplication'. This causes the dispatcher to stop waiting for 'mApplication' to add a focused
+ * window. Now, the "pruning input queue" behaviour should kick in, and the dispatcher should start
+ * dropping the events from its queue. Ensure that no crash occurs.
+ *
+ * In this test, we are setting long timeouts to prevent ANRs and events dropped due to being stale.
+ * This does not affect the test running time.
+ */
+TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvents) {
+    std::shared_ptr<FakeApplicationHandle> systemUiApplication =
+            std::make_shared<FakeApplicationHandle>();
+    systemUiApplication->setDispatchingTimeout(3000ms);
+    mFakePolicy->setStaleEventTimeout(3000ms);
+    sp<FakeWindowHandle> navigationBar =
+            sp<FakeWindowHandle>::make(systemUiApplication, mDispatcher, "NavigationBar",
+                                       ui::LogicalDisplayId::DEFAULT);
+    navigationBar->setFocusable(false);
+    navigationBar->setWatchOutsideTouch(true);
+    navigationBar->setFrame(Rect(0, 0, 100, 100));
+
+    mApplication->setDispatchingTimeout(3000ms);
+    // 'mApplication' is already focused, but we call it again here to make it explicit.
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication);
+
+    std::shared_ptr<FakeApplicationHandle> anotherApplication =
+            std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> appWindow =
+            sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Another window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    appWindow->setFocusable(false);
+    appWindow->setFrame(Rect(100, 100, 200, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*navigationBar->getInfo(), *appWindow->getInfo()}, {}, 0, 0});
+    // 'mFocusedWindow' is no longer in the dispatcher window list, and therefore loses focus
+    mFocusedWindow->consumeFocusEvent(false);
+
+    // Touch down the navigation bar. It consumes the touch and injects a key into the dispatcher
+    // in response.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    navigationBar->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Key will not be sent anywhere because we have no focused window. It will remain pending.
+    // Pretend we are injecting KEYCODE_BACK, but it doesn't actually matter what key it is.
+    InputEventInjectionResult result =
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                      /*injectionTimeout=*/100ms,
+                      /*allowKeyRepeat=*/false);
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
+
+    // Finish the gesture - lift up finger and inject ACTION_UP key event
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0,
+                       ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                       /*injectionTimeout=*/100ms,
+                       /*allowKeyRepeat=*/false);
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
+    // The key that was injected is blocking the dispatcher, so the navigation bar shouldn't be
+    // getting any events yet.
+    navigationBar->assertNoEvents();
+
+    // Now touch "Another window". This touch is going to a different application than the one we
+    // are waiting for (which is 'mApplication').
+    // This should cause the dispatcher to drop the pending focus-dispatched events (like the key
+    // trying to be injected) and to continue processing the rest of the events in the original
+    // order.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150))
+                                      .build());
+    navigationBar->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    navigationBar->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE));
+    appWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    appWindow->assertNoEvents();
+    navigationBar->assertNoEvents();
+}
+
 // These tests ensure we cannot send touch events to a window that's positioned behind a window
 // that has feature NO_INPUT_CHANNEL.
 // Layout:
@@ -8703,16 +10659,16 @@
         InputDispatcherTest::SetUp();
 
         mApplication = std::make_shared<FakeApplicationHandle>();
-        mNoInputWindow =
-                sp<FakeWindowHandle>::make(mApplication, mDispatcher,
-                                           "Window without input channel", ADISPLAY_ID_DEFAULT,
-                                           /*token=*/std::make_optional<sp<IBinder>>(nullptr));
+        mNoInputWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher,
+                                                    "Window without input channel",
+                                                    ui::LogicalDisplayId::DEFAULT,
+                                                    /*createInputChannel=*/false);
         mNoInputWindow->setNoInputChannel(true);
         mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
         // It's perfectly valid for this window to not have an associated input channel
 
         mBottomWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Bottom window",
-                                                   ADISPLAY_ID_DEFAULT);
+                                                   ui::LogicalDisplayId::DEFAULT);
         mBottomWindow->setFrame(Rect(0, 0, 100, 100));
 
         mDispatcher->onWindowInfosChanged(
@@ -8729,8 +10685,8 @@
     PointF touchedPoint = {10, 10};
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {touchedPoint}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {touchedPoint}));
 
     mNoInputWindow->assertNoEvents();
     // Even though the window 'mNoInputWindow' positioned above 'mBottomWindow' does not have
@@ -8747,7 +10703,7 @@
        NoInputChannelFeature_DropsTouchesWithValidChannel) {
     mNoInputWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher,
                                                 "Window with input channel and NO_INPUT_CHANNEL",
-                                                ADISPLAY_ID_DEFAULT);
+                                                ui::LogicalDisplayId::DEFAULT);
 
     mNoInputWindow->setNoInputChannel(true);
     mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
@@ -8757,8 +10713,8 @@
     PointF touchedPoint = {10, 10};
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {touchedPoint}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {touchedPoint}));
 
     mNoInputWindow->assertNoEvents();
     mBottomWindow->assertNoEvents();
@@ -8773,10 +10729,10 @@
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
         mApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
-        mMirror = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindowMirror",
-                                             ADISPLAY_ID_DEFAULT, mWindow->getToken());
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
+        mMirror = mWindow->clone(ui::LogicalDisplayId::DEFAULT);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
         mWindow->setFocusable(true);
         mMirror->setFocusable(true);
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
@@ -8791,7 +10747,7 @@
     mWindow->consumeFocusEvent(true);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 }
 
 // A focused & mirrored window remains focused only if the window and its mirror are both
@@ -8799,14 +10755,14 @@
 TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedIfAllWindowsFocusable) {
     setFocusedWindow(mMirror);
 
-    // window gets focused
+    // window gets focused because it is above the mirror
     mWindow->consumeFocusEvent(true);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     mMirror->setFocusable(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
@@ -8828,20 +10784,20 @@
     mWindow->consumeFocusEvent(true);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     mMirror->setVisible(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     mWindow->setVisible(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
@@ -8862,20 +10818,20 @@
     mWindow->consumeFocusEvent(true);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     // single window is removed but the window token remains focused
     mDispatcher->onWindowInfosChanged({{*mMirror->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mMirror->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mMirror->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     // Both windows are removed
     mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
@@ -8897,7 +10853,7 @@
     // Injected key goes to pending queue.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+                        ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE));
 
     mMirror->setVisible(true);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
@@ -8905,7 +10861,7 @@
     // window gets focused
     mWindow->consumeFocusEvent(true);
     // window gets the pending key event
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 class InputDispatcherPointerCaptureTests : public InputDispatcherTest {
@@ -8917,13 +10873,14 @@
     void SetUp() override {
         InputDispatcherTest::SetUp();
         mApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFocusable(true);
-        mSecondWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+        mSecondWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2",
+                                                   ui::LogicalDisplayId::DEFAULT);
         mSecondWindow->setFocusable(true);
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
         mDispatcher->onWindowInfosChanged(
                 {{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
 
@@ -8938,7 +10895,7 @@
     PointerCaptureRequest requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window,
                                                          bool enabled) {
         mDispatcher->requestPointerCapture(window->getToken(), enabled);
-        auto request = mFakePolicy->assertSetPointerCaptureCalled(enabled);
+        auto request = mFakePolicy->assertSetPointerCaptureCalled(window, enabled);
         notifyPointerCaptureChanged(request);
         window->consumeCaptureEvent(enabled);
         return request;
@@ -8971,7 +10928,7 @@
     mWindow->consumeCaptureEvent(false);
     mWindow->consumeFocusEvent(false);
     mSecondWindow->consumeFocusEvent(true);
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
 
     // Ensure that additional state changes from InputReader are not sent to the window.
     notifyPointerCaptureChanged({});
@@ -8990,7 +10947,7 @@
     notifyPointerCaptureChanged(request);
 
     // Ensure that Pointer Capture is disabled.
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
     mWindow->consumeCaptureEvent(false);
     mWindow->assertNoEvents();
 }
@@ -9000,13 +10957,13 @@
 
     // The first window loses focus.
     setFocusedWindow(mSecondWindow);
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
     mWindow->consumeCaptureEvent(false);
 
     // Request Pointer Capture from the second window before the notification from InputReader
     // arrives.
     mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
-    auto request = mFakePolicy->assertSetPointerCaptureCalled(true);
+    auto request = mFakePolicy->assertSetPointerCaptureCalled(mSecondWindow, true);
 
     // InputReader notifies Pointer Capture was disabled (because of the focus change).
     notifyPointerCaptureChanged({});
@@ -9021,11 +10978,11 @@
 TEST_F(InputDispatcherPointerCaptureTests, EnableRequestFollowsSequenceNumbers) {
     // App repeatedly enables and disables capture.
     mDispatcher->requestPointerCapture(mWindow->getToken(), true);
-    auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+    auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
     mDispatcher->requestPointerCapture(mWindow->getToken(), false);
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
     mDispatcher->requestPointerCapture(mWindow->getToken(), true);
-    auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+    auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
 
     // InputReader notifies that PointerCapture has been enabled for the first request. Since the
     // first request is now stale, this should do nothing.
@@ -9042,10 +10999,10 @@
 
     // App toggles pointer capture off and on.
     mDispatcher->requestPointerCapture(mWindow->getToken(), false);
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
 
     mDispatcher->requestPointerCapture(mWindow->getToken(), true);
-    auto enableRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+    auto enableRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
 
     // InputReader notifies that the latest "enable" request was processed, while skipping over the
     // preceding "disable" request.
@@ -9097,6 +11054,26 @@
     mWindow->assertNoEvents();
 }
 
+using InputDispatcherPointerCaptureDeathTest = InputDispatcherPointerCaptureTests;
+
+TEST_F(InputDispatcherPointerCaptureDeathTest,
+       NotifyPointerCaptureChangedWithWrongTokenAbortsDispatcher) {
+    testing::GTEST_FLAG(death_test_style) = "threadsafe";
+    ScopedSilentDeath _silentDeath;
+
+    mDispatcher->requestPointerCapture(mWindow->getToken(), true);
+    auto request = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
+
+    // Dispatch a pointer changed event with a wrong token.
+    request.window = mSecondWindow->getToken();
+    ASSERT_DEATH(
+            {
+                notifyPointerCaptureChanged(request);
+                mSecondWindow->consumeCaptureEvent(true);
+            },
+            "Unexpected requested window for Pointer Capture.");
+}
+
 class InputDispatcherUntrustedTouchesTest : public InputDispatcherTest {
 protected:
     constexpr static const float MAXIMUM_OBSCURING_OPACITY = 0.8;
@@ -9142,7 +11119,7 @@
     sp<FakeWindowHandle> getWindow(gui::Uid uid, std::string name) {
         std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
         sp<FakeWindowHandle> window =
-                sp<FakeWindowHandle>::make(app, mDispatcher, name, ADISPLAY_ID_DEFAULT);
+                sp<FakeWindowHandle>::make(app, mDispatcher, name, ui::LogicalDisplayId::DEFAULT);
         // Generate an arbitrary PID based on the UID
         window->setOwnerInfo(gui::Pid{static_cast<pid_t>(1777 + (uid.val() % 10000))}, uid);
         return window;
@@ -9150,8 +11127,8 @@
 
     void touch(const std::vector<PointF>& points = {PointF{100, 200}}) {
         mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     points));
+                                                     AINPUT_SOURCE_TOUCHSCREEN,
+                                                     ui::LogicalDisplayId::DEFAULT, points));
     }
 };
 
@@ -9516,20 +11493,21 @@
     void SetUp() override {
         InputDispatcherTest::SetUp();
         mApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFrame(Rect(0, 0, 100, 100));
 
-        mSecondWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+        mSecondWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2",
+                                                   ui::LogicalDisplayId::DEFAULT);
         mSecondWindow->setFrame(Rect(100, 0, 200, 100));
 
-        mSpyWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "SpyWindow", ADISPLAY_ID_DEFAULT);
+        mSpyWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "SpyWindow",
+                                                ui::LogicalDisplayId::DEFAULT);
         mSpyWindow->setSpy(true);
         mSpyWindow->setTrustedOverlay(true);
         mSpyWindow->setFrame(Rect(0, 0, 200, 100));
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
         mDispatcher->onWindowInfosChanged(
                 {{*mSpyWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()},
                  {},
@@ -9542,7 +11520,7 @@
             case AINPUT_SOURCE_TOUCHSCREEN:
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                           injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
-                                           ADISPLAY_ID_DEFAULT, {50, 50}))
+                                           ui::LogicalDisplayId::DEFAULT, {50, 50}))
                         << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
                 break;
             case AINPUT_SOURCE_STYLUS:
@@ -9574,9 +11552,9 @@
         }
 
         // Window should receive motion event.
-        mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+        mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
         // Spy window should also receive motion event
-        mSpyWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+        mSpyWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     }
 
     // Start performing drag, we will create a drag window and transfer touch to it.
@@ -9588,8 +11566,8 @@
         }
 
         // The drag window covers the entire display
-        mDragWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
+        mDragWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "DragWindow",
+                                                 ui::LogicalDisplayId::DEFAULT);
         mDragWindow->setTouchableRegion(Region{{0, 0, 0, 0}});
         mDispatcher->onWindowInfosChanged({{*mDragWindow->getInfo(), *mSpyWindow->getInfo(),
                                             *mWindow->getInfo(), *mSecondWindow->getInfo()},
@@ -9599,11 +11577,12 @@
 
         // Transfer touch focus to the drag window
         bool transferred =
-                mDispatcher->transferTouchFocus(mWindow->getToken(), mDragWindow->getToken(),
-                                                /*isDragDrop=*/true);
+                mDispatcher->transferTouchGesture(mWindow->getToken(), mDragWindow->getToken(),
+                                                  /*isDragDrop=*/true);
         if (transferred) {
             mWindow->consumeMotionCancel();
-            mDragWindow->consumeMotionDown();
+            mDragWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
         }
         return transferred;
     }
@@ -9615,35 +11594,38 @@
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->consumeDragEvent(false, 50, 50);
 
     // Move back to original window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->consumeDragEvent(true, -50, 50);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -9678,27 +11660,29 @@
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->consumeDragEvent(false, 50, 50);
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -9723,7 +11707,7 @@
 
     // Receives cancel for first pointer after next pointer down
     mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
-    mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    mSpyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithPointerIds({1})));
     mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
 
     mSpyWindow->assertNoEvents();
@@ -9733,18 +11717,19 @@
     mDragWindow->assertNoEvents();
 
     const MotionEvent firstFingerMoveEvent =
-            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+            MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(60).y(60))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, firstFingerMoveEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Drag window should still receive the new event
-    mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    mDragWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
     mDragWindow->assertNoEvents();
 }
 
@@ -9759,7 +11744,8 @@
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
@@ -9771,7 +11757,8 @@
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
@@ -9784,7 +11771,7 @@
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -9800,27 +11787,29 @@
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->assertNoEvents();
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, nullptr);
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -9831,14 +11820,14 @@
     mWindow->setPreventSplitting(true);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(75).y(50))
@@ -9856,16 +11845,16 @@
 TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) {
     // First down on second window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {150, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    mSecondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mSecondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // Second down on first window.
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -9874,7 +11863,8 @@
               injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    mSecondWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
 
     // Perform drag and drop from first window.
     ASSERT_TRUE(startDrag(false));
@@ -9889,7 +11879,8 @@
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT));
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->consumeMotionMove();
 
@@ -9903,7 +11894,7 @@
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT));
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mWindow->getToken());
     mWindow->assertNoEvents();
     mSecondWindow->consumeMotionMove();
@@ -9930,8 +11921,7 @@
                                         .displayId(SECOND_DISPLAY_ID)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    windowInSecondary->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
-                                    SECOND_DISPLAY_ID, /*expectedFlag=*/0);
+    windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID, /*expectedFlag=*/0);
     // Update window again.
     mDispatcher->onWindowInfosChanged(
             {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(),
@@ -9943,27 +11933,29 @@
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->consumeDragEvent(false, 50, 50);
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -9981,7 +11973,8 @@
                                                          .y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
@@ -9995,7 +11988,8 @@
                                                          .y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->consumeDragEvent(false, 50, 50);
 
@@ -10009,7 +12003,7 @@
                                                          .y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -10022,8 +12016,8 @@
 TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) {
     // Down on second window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {150, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionDown());
@@ -10032,7 +12026,7 @@
     // Down on first window
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
@@ -10050,7 +12044,8 @@
     // Trigger cancel
     mDispatcher->cancelCurrentTouch();
     ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionCancel());
-    ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel());
+    ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT,
+                                                             AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE));
     ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionCancel());
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
@@ -10062,14 +12057,14 @@
 
     // Inject a simple gesture, ensure dispatcher not crashed
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               PointF{50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, PointF{50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
 
     const MotionEvent moveEvent =
             MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -10079,7 +12074,7 @@
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionMove());
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionUp());
@@ -10089,7 +12084,7 @@
     // Start hovering over the window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE,
-                                ADISPLAY_ID_DEFAULT, {50, 50}));
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}));
 
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)));
     ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)));
@@ -10102,23 +12097,25 @@
 
 TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setDropInput(true);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
     window->assertNoEvents();
 
@@ -10126,12 +12123,13 @@
     window->setDropInput(false);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT));
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     window->assertNoEvents();
 }
 
@@ -10140,16 +12138,17 @@
             std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(obscuringApplication, mDispatcher, "obscuringWindow",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     obscuringWindow->setFrame(Rect(0, 0, 50, 50));
     obscuringWindow->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
     obscuringWindow->setTouchable(false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setDropInputIfObscured(true);
     window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged(
             {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
@@ -10157,13 +12156,14 @@
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
@@ -10171,12 +12171,14 @@
     mDispatcher->onWindowInfosChanged(
             {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT));
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                              AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
     window->assertNoEvents();
 }
 
@@ -10185,16 +12187,17 @@
             std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(obscuringApplication, mDispatcher, "obscuringWindow",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     obscuringWindow->setFrame(Rect(0, 0, 50, 50));
     obscuringWindow->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
     obscuringWindow->setTouchable(false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setDropInputIfObscured(true);
     window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged(
             {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
@@ -10202,25 +12205,27 @@
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     // When the window is no longer obscured because it went on top, it should get input
     mDispatcher->onWindowInfosChanged(
             {{*window->getInfo(), *obscuringWindow->getInfo()}, {}, 0, 0});
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT));
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     window->assertNoEvents();
 }
 
@@ -10237,18 +12242,19 @@
 
         mApp = std::make_shared<FakeApplicationHandle>();
         mSecondaryApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFocusable(true);
         setFocusedWindow(mWindow);
-        mSecondWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+        mSecondWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2",
+                                                   ui::LogicalDisplayId::DEFAULT);
         mSecondWindow->setFocusable(true);
         mThirdWindow =
                 sp<FakeWindowHandle>::make(mSecondaryApp, mDispatcher,
                                            "TestWindow3_SecondaryDisplay", SECOND_DISPLAY_ID);
         mThirdWindow->setFocusable(true);
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
         mDispatcher->onWindowInfosChanged(
                 {{*mWindow->getInfo(), *mSecondWindow->getInfo(), *mThirdWindow->getInfo()},
                  {},
@@ -10259,7 +12265,8 @@
 
         // Set main display initial touch mode to InputDispatcher::kDefaultInTouchMode.
         if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID,
-                                        WINDOW_UID, /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)) {
+                                        WINDOW_UID, /*hasPermission=*/true,
+                                        ui::LogicalDisplayId::DEFAULT)) {
             mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
             mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
             mThirdWindow->assertNoEvents();
@@ -10278,7 +12285,7 @@
     void changeAndVerifyTouchModeInMainDisplayOnly(bool inTouchMode, gui::Pid pid, gui::Uid uid,
                                                    bool hasPermission) {
         ASSERT_TRUE(mDispatcher->setInTouchMode(inTouchMode, pid, uid, hasPermission,
-                                                ADISPLAY_ID_DEFAULT));
+                                                ui::LogicalDisplayId::DEFAULT));
         mWindow->consumeTouchModeEvent(inTouchMode);
         mSecondWindow->consumeTouchModeEvent(inTouchMode);
         mThirdWindow->assertNoEvents();
@@ -10299,7 +12306,7 @@
     mWindow->setOwnerInfo(gui::Pid::INVALID, gui::Uid::INVALID);
     ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid,
                                              ownerUid, /*hasPermission=*/false,
-                                             ADISPLAY_ID_DEFAULT));
+                                             ui::LogicalDisplayId::DEFAULT));
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -10317,7 +12324,8 @@
     const WindowInfo& windowInfo = *mWindow->getInfo();
     ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode,
                                              windowInfo.ownerPid, windowInfo.ownerUid,
-                                             /*hasPermission=*/true, ADISPLAY_ID_DEFAULT));
+                                             /*hasPermission=*/true,
+                                             ui::LogicalDisplayId::DEFAULT));
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -10335,9 +12343,9 @@
 TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInteractedWindow) {
     // Interact with the window first.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // Then remove focus.
     mWindow->setFocusable(false);
@@ -10347,7 +12355,8 @@
     const WindowInfo& windowInfo = *mWindow->getInfo();
     ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode,
                                             windowInfo.ownerPid, windowInfo.ownerUid,
-                                            /*hasPermission=*/false, ADISPLAY_ID_DEFAULT));
+                                            /*hasPermission=*/false,
+                                            ui::LogicalDisplayId::DEFAULT));
 }
 
 class InputDispatcherSpyWindowTest : public InputDispatcherTest {
@@ -10357,8 +12366,9 @@
                 std::make_shared<FakeApplicationHandle>();
         std::string name = "Fake Spy ";
         name += std::to_string(mSpyCount++);
-        sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                              name.c_str(), ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> spy =
+                sp<FakeWindowHandle>::make(application, mDispatcher, name.c_str(),
+                                           ui::LogicalDisplayId::DEFAULT);
         spy->setSpy(true);
         spy->setTrustedOverlay(true);
         return spy;
@@ -10369,7 +12379,7 @@
                 std::make_shared<FakeApplicationHandle>();
         sp<FakeWindowHandle> window =
                 sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
-                                           ADISPLAY_ID_DEFAULT);
+                                           ui::LogicalDisplayId::DEFAULT);
         window->setFocusable(true);
         return window;
     }
@@ -10400,9 +12410,10 @@
     mDispatcher->onWindowInfosChanged({{*spy->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -10440,7 +12451,8 @@
     }
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     std::vector<size_t> eventOrder;
@@ -10478,9 +12490,10 @@
     mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     spy->assertNoEvents();
 }
 
@@ -10497,20 +12510,22 @@
 
     // Inject an event outside the spy window's touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->assertNoEvents();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                             ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionUp();
     spy->assertNoEvents();
 
     // Inject an event inside the spy window's touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {5, 10}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {5, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->consumeMotionDown();
@@ -10531,8 +12546,8 @@
 
     // Inject an event outside the spy window's frame and touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->consumeMotionOutsideWithZeroedCoords();
@@ -10553,8 +12568,8 @@
             {{*spy->getInfo(), *windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowLeft->consumeMotionDown();
     spy->consumeMotionDown();
@@ -10585,8 +12600,8 @@
     mDispatcher->onWindowInfosChanged({{*spyRight->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spyRight->assertNoEvents();
@@ -10622,16 +12637,16 @@
 
     // First finger down, no window touched.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     window->assertNoEvents();
 
     // Second finger down on window, the window should receive touch down.
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -10641,7 +12656,7 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     spy->consumeMotionPointerDown(/*pointerIndex=*/1);
 }
 
@@ -10660,11 +12675,11 @@
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeKeyDown(ADISPLAY_ID_NONE);
+    window->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeKeyUp(ADISPLAY_ID_NONE);
+    window->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     spy->assertNoEvents();
 }
@@ -10683,7 +12698,8 @@
             {{*spy1->getInfo(), *spy2->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy1->consumeMotionDown();
@@ -10698,7 +12714,7 @@
     // The rest of the gesture should only be sent to the second spy window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT))
+                                ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy2->consumeMotionMove();
     spy1->assertNoEvents();
@@ -10715,19 +12731,21 @@
     mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     window->releaseChannel();
 
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                             ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -10742,8 +12760,8 @@
 
     // First finger down on the window and the spy.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionDown();
     window->consumeMotionDown();
@@ -10755,7 +12773,7 @@
     // Second finger down on the window and spy, but the window should not receive the pointer down.
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -10770,7 +12788,7 @@
     // Third finger goes down outside all windows, so injection should fail.
     const MotionEvent thirdFingerDownEvent =
             MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -10798,15 +12816,15 @@
 
     // First finger down on the window only
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {150, 150}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {150, 150}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
 
     // Second finger down on the spy and window
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
@@ -10821,7 +12839,7 @@
     // Third finger down on the spy and window
     const MotionEvent thirdFingerDownEvent =
             MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
@@ -10836,8 +12854,10 @@
 
     // Spy window pilfers the pointers.
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
-    window->consumeMotionPointerUp(/*idx=*/2, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
-    window->consumeMotionPointerUp(/*idx=*/1, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+    window->consumeMotionPointerUp(/*idx=*/2, ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_CANCELED);
+    window->consumeMotionPointerUp(/*idx=*/1, ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_CANCELED);
 
     spy->assertNoEvents();
     window->assertNoEvents();
@@ -10858,8 +12878,8 @@
 
     // First finger down on both spy and window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {10, 10}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {10, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->consumeMotionDown();
@@ -10867,7 +12887,7 @@
     // Second finger down on the spy and window
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -10901,8 +12921,8 @@
 
     // First finger down on both window and spy
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {10, 10}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {10, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->consumeMotionDown();
@@ -10914,7 +12934,7 @@
     // Second finger down on the window only
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
@@ -10939,7 +12959,8 @@
  * Pilfer from spy window.
  * Check that the pilfering only affects the pointers that are actually being received by the spy.
  */
-TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) {
+TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     sp<FakeWindowHandle> spy = createSpy();
     spy->setFrame(Rect(0, 0, 200, 200));
     sp<FakeWindowHandle> leftWindow = createForeground();
@@ -10997,6 +13018,83 @@
     rightWindow->assertNoEvents();
 }
 
+/**
+ * A window on the left and a window on the right. Also, a spy window that's above all of the
+ * windows, and spanning both left and right windows.
+ * Send simultaneous motion streams from two different devices, one to the left window, and another
+ * to the right window.
+ * Pilfer from spy window.
+ * Check that the pilfering affects all of the pointers that are actually being received by the spy.
+ * The spy should receive both the touch and the stylus events after pilfer.
+ */
+TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    sp<FakeWindowHandle> spy = createSpy();
+    spy->setFrame(Rect(0, 0, 200, 200));
+    sp<FakeWindowHandle> leftWindow = createForeground();
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> rightWindow = createForeground();
+    rightWindow->setFrame(Rect(100, 0, 200, 100));
+
+    constexpr int32_t stylusDeviceId = 1;
+    constexpr int32_t touchDeviceId = 2;
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spy->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    // Stylus down on left window and spy
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Finger down on right window and spy
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                    .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Act: pilfer from spy. Spy is currently receiving touch events.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+
+    // Continue movements from both stylus and touch. Touch and stylus will be delivered to spy
+    // Instead of sending the two MOVE events for each input device together, and then receiving
+    // them both, process them one at at time. InputConsumer is always in the batching mode, which
+    // means that the two MOVE events will be initially put into a batch. Once the events are
+    // batched, the 'consume' call may result in any of the MOVE events to be sent first (depending
+    // on the implementation of InputConsumer), which would mean that the order of the received
+    // events could be different depending on whether there are 1 or 2 events pending in the
+    // InputChannel at the time the test calls 'consume'. To make assertions simpler here, and to
+    // avoid this confusing behaviour, send and receive each MOVE event separately.
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(52))
+                                      .build());
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52))
+                    .build());
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    spy->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherPilferPointersTest, NoPilferingWithHoveringPointers) {
     auto window = createForeground();
     auto spy = createSpy();
@@ -11021,9 +13119,9 @@
     std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupStylusOverlayScenario() {
         std::shared_ptr<FakeApplicationHandle> overlayApplication =
                 std::make_shared<FakeApplicationHandle>();
-        sp<FakeWindowHandle> overlay =
-                sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
-                                           "Stylus interceptor window", ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
+                                                                  "Stylus interceptor window",
+                                                                  ui::LogicalDisplayId::DEFAULT);
         overlay->setFocusable(false);
         overlay->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
         overlay->setTouchable(false);
@@ -11034,11 +13132,11 @@
                 std::make_shared<FakeApplicationHandle>();
         sp<FakeWindowHandle> window =
                 sp<FakeWindowHandle>::make(application, mDispatcher, "Application window",
-                                           ADISPLAY_ID_DEFAULT);
+                                           ui::LogicalDisplayId::DEFAULT);
         window->setFocusable(true);
         window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
         mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
         setFocusedWindow(window);
         window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
@@ -11048,13 +13146,13 @@
     void sendFingerEvent(int32_t action) {
         mDispatcher->notifyMotion(
                 generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
-                                   ADISPLAY_ID_DEFAULT, {PointF{20, 20}}));
+                                   ui::LogicalDisplayId::DEFAULT, {PointF{20, 20}}));
     }
 
     void sendStylusEvent(int32_t action) {
         NotifyMotionArgs motionArgs =
                 generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
-                                   ADISPLAY_ID_DEFAULT, {PointF{30, 40}});
+                                   ui::LogicalDisplayId::DEFAULT, {PointF{30, 40}});
         motionArgs.pointerProperties[0].toolType = ToolType::STYLUS;
         mDispatcher->notifyMotion(motionArgs);
     }
@@ -11158,7 +13256,7 @@
 
     InputEventInjectionResult injectTargetedMotion(int32_t action) const {
         return injectMotionEvent(*mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN,
-                                 ADISPLAY_ID_DEFAULT, {100, 200},
+                                 ui::LogicalDisplayId::DEFAULT, {100, 200},
                                  {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                   AMOTION_EVENT_INVALID_CURSOR_POSITION},
                                  INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT,
@@ -11166,7 +13264,8 @@
     }
 
     InputEventInjectionResult injectTargetedKey(int32_t action) const {
-        return inputdispatcher::injectKey(*mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE,
+        return inputdispatcher::injectKey(*mDispatcher, action, /*repeatCount=*/0,
+                                          ui::LogicalDisplayId::INVALID,
                                           InputEventInjectionSync::WAIT_FOR_RESULT,
                                           INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid},
                                           mPolicyFlags);
@@ -11175,8 +13274,9 @@
     sp<FakeWindowHandle> createWindow(const char* name) const {
         std::shared_ptr<FakeApplicationHandle> overlayApplication =
                 std::make_shared<FakeApplicationHandle>();
-        sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
-                                                                 name, ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> window =
+                sp<FakeWindowHandle>::make(overlayApplication, mDispatcher, name,
+                                           ui::LogicalDisplayId::DEFAULT);
         window->setOwnerInfo(mPid, mUid);
         return window;
     }
@@ -11198,7 +13298,7 @@
 
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
               owner.injectTargetedKey(AKEY_EVENT_ACTION_DOWN));
-    window->consumeKeyDown(ADISPLAY_ID_NONE);
+    window->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 }
 
 TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) {
@@ -11263,7 +13363,7 @@
     // A user that has injection permission can inject into any window.
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT));
+                                ui::LogicalDisplayId::DEFAULT));
     randosSpy->consumeMotionDown();
     window->consumeMotionDown();
 
@@ -11271,7 +13371,7 @@
     randosSpy->consumeFocusEvent(true);
 
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher));
-    randosSpy->consumeKeyDown(ADISPLAY_ID_NONE);
+    randosSpy->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     window->assertNoEvents();
 }
 
@@ -11298,13 +13398,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
-                                                           ADISPLAY_ID_DEFAULT);
+                                                           ui::LogicalDisplayId::DEFAULT);
     left->setFrame(Rect(0, 0, 100, 100));
-    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     right->setFrame(Rect(100, 0, 200, 100));
-    sp<FakeWindowHandle> spy =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window",
+                                                          ui::LogicalDisplayId::DEFAULT);
     spy->setFrame(Rect(0, 0, 200, 100));
     spy->setTrustedOverlay(true);
     spy->setSpy(true);
@@ -11321,11 +13422,14 @@
     left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
     spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 
     // Hover move to the right window.
@@ -11338,11 +13442,14 @@
     right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
     spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
 
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
 
     // Stop hovering.
@@ -11354,11 +13461,14 @@
     right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
     spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
 
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 }
 
@@ -11366,13 +13476,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
-                                                           ADISPLAY_ID_DEFAULT);
+                                                           ui::LogicalDisplayId::DEFAULT);
     left->setFrame(Rect(0, 0, 100, 100));
-    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     right->setFrame(Rect(100, 0, 200, 100));
-    sp<FakeWindowHandle> spy =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window",
+                                                          ui::LogicalDisplayId::DEFAULT);
     spy->setFrame(Rect(0, 0, 200, 100));
     spy->setTrustedOverlay(true);
     spy->setSpy(true);
@@ -11389,11 +13500,14 @@
     left->consumeMotionDown();
     spy->consumeMotionDown();
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 
     // Second pointer down on right window.
@@ -11407,17 +13521,23 @@
     right->consumeMotionDown();
     spy->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/1));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/1));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/1));
 
     // Second pointer up.
@@ -11431,17 +13551,23 @@
     right->consumeMotionUp();
     spy->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/1));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/1));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/1));
 
     // First pointer up.
@@ -11453,29 +13579,36 @@
     left->consumeMotionUp();
     spy->consumeMotionUp();
 
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 }
 
-TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
-                                                           ADISPLAY_ID_DEFAULT);
+                                                           ui::LogicalDisplayId::DEFAULT);
     left->setFrame(Rect(0, 0, 100, 100));
-    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     right->setFrame(Rect(100, 0, 200, 100));
 
     mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0});
 
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 
     // Hover move into the window.
@@ -11489,7 +13622,8 @@
 
     left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
 
     // Move the mouse with another device. This cancels the hovering pointer from the first device.
@@ -11506,9 +13640,10 @@
 
     // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets
     // a HOVER_EXIT from the first device.
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
                                                SECOND_DEVICE_ID,
                                                /*pointerId=*/0));
 
@@ -11524,9 +13659,88 @@
 
     left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
     right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                SECOND_DEVICE_ID,
+                                                /*pointerId=*/0));
+}
+
+/**
+ * TODO(b/313689709) - correctly support multiple mouse devices, because they should be controlling
+ * the same cursor, and therefore have a shared motion event stream.
+ */
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+                                                           ui::LogicalDisplayId::DEFAULT);
+    left->setFrame(Rect(0, 0, 100, 100));
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    right->setFrame(Rect(100, 0, 200, 100));
+
+    mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
+                                                /*pointerId=*/0));
+
+    // Hover move into the window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50))
+                    .rawXCursorPosition(50)
+                    .rawYCursorPosition(50)
+                    .deviceId(DEVICE_ID)
+                    .build());
+
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
+                                               /*pointerId=*/0));
+
+    // Move the mouse with another device
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(51).y(50))
+                    .rawXCursorPosition(51)
+                    .rawYCursorPosition(50)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .build());
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets
+    // a HOVER_EXIT from the first device.
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               SECOND_DEVICE_ID,
+                                               /*pointerId=*/0));
+
+    // Move the mouse outside the window. Document the current behavior, where the window does not
+    // receive HOVER_EXIT even though the mouse left the window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(150).y(50))
+                    .rawXCursorPosition(150)
+                    .rawYCursorPosition(50)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .build());
+
+    right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
                                                 SECOND_DEVICE_ID,
                                                 /*pointerId=*/0));
 }
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 36be684..fea1349 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -19,37 +19,45 @@
 #include <InputReaderBase.h>
 #include <gtest/gtest.h>
 #include <ui/Rotation.h>
+#include <utils/Timers.h>
+
+#include "NotifyArgs.h"
 
 namespace android {
 
+using testing::_;
 using testing::Return;
 
-void InputMapperUnitTest::SetUp() {
-    mFakePointerController = std::make_shared<FakePointerController>();
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
+void InputMapperUnitTest::SetUpWithBus(int bus) {
     mFakePolicy = sp<FakeInputReaderPolicy>::make();
 
-    EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
-            .WillRepeatedly(Return(mFakePointerController));
-
     EXPECT_CALL(mMockInputReaderContext, getPolicy()).WillRepeatedly(Return(mFakePolicy.get()));
 
     EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
-    InputDeviceIdentifier identifier;
-    identifier.name = "device";
-    identifier.location = "USB1";
-    identifier.bus = 0;
 
-    EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)).WillRepeatedly(Return(identifier));
+    mIdentifier.name = "device";
+    mIdentifier.location = "USB1";
+    mIdentifier.bus = bus;
+    EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID))
+            .WillRepeatedly(Return(mIdentifier));
+    EXPECT_CALL(mMockEventHub, getConfiguration(EVENTHUB_ID)).WillRepeatedly([&](int32_t) {
+        return mPropertyMap;
+    });
+}
+
+void InputMapperUnitTest::createDevice() {
     mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
-                                            /*generation=*/2, identifier);
+                                            /*generation=*/2, mIdentifier);
+    mDevice->addEmptyEventHubDevice(EVENTHUB_ID);
     mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
+    std::list<NotifyArgs> args =
+            mDevice->configure(systemTime(), mReaderConfiguration, /*changes=*/{});
+    ASSERT_THAT(args, testing::ElementsAre(testing::VariantWith<NotifyDeviceResetArgs>(_)));
 }
 
 void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
                                     int32_t resolution) {
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, _))
             .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
                 outAxisInfo->valid = valid;
                 outAxisInfo->minValue = min;
@@ -164,8 +172,8 @@
     return device;
 }
 
-void InputMapperTest::setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
-                                                   ui::Rotation orientation,
+void InputMapperTest::setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width,
+                                                   int32_t height, ui::Rotation orientation,
                                                    const std::string& uniqueId,
                                                    std::optional<uint8_t> physicalPort,
                                                    ViewportType viewportType) {
@@ -215,8 +223,8 @@
     return generatedArgs;
 }
 
-void InputMapperTest::assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source,
-                                        float min, float max, float flat, float fuzz) {
+void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min,
+                       float max, float flat, float fuzz) {
     const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source);
     ASSERT_TRUE(range != nullptr) << "Axis: " << axis << " Source: " << source;
     ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source;
@@ -227,11 +235,9 @@
     ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source;
 }
 
-void InputMapperTest::assertPointerCoords(const PointerCoords& coords, float x, float y,
-                                          float pressure, float size, float touchMajor,
-                                          float touchMinor, float toolMajor, float toolMinor,
-                                          float orientation, float distance,
-                                          float scaledAxisEpsilon) {
+void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, float size,
+                         float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+                         float orientation, float distance, float scaledAxisEpsilon) {
     ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon);
     ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon);
     ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON);
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 05b0e97..5bd8cda 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -32,6 +32,7 @@
 #include "InterfaceMocks.h"
 #include "TestConstants.h"
 #include "TestInputListener.h"
+#include "input/PropertyMap.h"
 
 namespace android {
 
@@ -39,9 +40,15 @@
 protected:
     static constexpr int32_t EVENTHUB_ID = 1;
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
-    static constexpr float INITIAL_CURSOR_X = 400;
-    static constexpr float INITIAL_CURSOR_Y = 240;
-    virtual void SetUp() override;
+    virtual void SetUp() override { SetUpWithBus(0); }
+    virtual void SetUpWithBus(int bus);
+
+    /**
+     * Initializes mDevice and mDeviceContext. When this happens, mDevice takes a copy of
+     * mPropertyMap, so tests that need to set configuration properties should do so before calling
+     * this. Others will most likely want to call it in their SetUp method.
+     */
+    void createDevice();
 
     void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);
 
@@ -54,9 +61,9 @@
     std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value);
     std::list<NotifyArgs> process(nsecs_t when, int32_t type, int32_t code, int32_t value);
 
+    InputDeviceIdentifier mIdentifier;
     MockEventHubInterface mMockEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
-    std::shared_ptr<FakePointerController> mFakePointerController;
     MockInputReaderContext mMockInputReaderContext;
     std::unique_ptr<InputDevice> mDevice;
 
@@ -64,6 +71,7 @@
     InputReaderConfiguration mReaderConfiguration;
     // The mapper should be created by the subclasses.
     std::unique_ptr<InputMapper> mMapper;
+    PropertyMap mPropertyMap;
 };
 
 /**
@@ -120,7 +128,7 @@
                                                  args...);
     }
 
-    void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
+    void setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
                                       ui::Rotation orientation, const std::string& uniqueId,
                                       std::optional<uint8_t> physicalPort,
                                       ViewportType viewportType);
@@ -130,13 +138,13 @@
     void resetMapper(InputMapper& mapper, nsecs_t when);
 
     std::list<NotifyArgs> handleTimeout(InputMapper& mapper, nsecs_t when);
-
-    static void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source,
-                                  float min, float max, float flat, float fuzz);
-    static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure,
-                                    float size, float touchMajor, float touchMinor, float toolMajor,
-                                    float toolMinor, float orientation, float distance,
-                                    float scaledAxisEpsilon = 1.f);
 };
 
+void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min,
+                       float max, float flat, float fuzz);
+
+void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, float size,
+                         float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+                         float orientation, float distance, float scaledAxisEpsilon = 1.f);
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp
index 4b42f4b..bdf156c 100644
--- a/services/inputflinger/tests/InputProcessorConverter_test.cpp
+++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp
@@ -17,7 +17,6 @@
 #include "../InputCommonConverter.h"
 
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <utils/BitSet.h>
 
 using namespace aidl::android::hardware::input;
@@ -39,7 +38,7 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5);
     static constexpr nsecs_t downTime = 2;
     NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2,
-                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::LogicalDisplayId::DEFAULT,
                                 /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
                                 /*flags=*/0, AMETA_NONE, /*buttonState=*/0,
                                 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp
index 3b7cbfa..f7e5e67 100644
--- a/services/inputflinger/tests/InputProcessor_test.cpp
+++ b/services/inputflinger/tests/InputProcessor_test.cpp
@@ -16,7 +16,6 @@
 
 #include "../InputProcessor.h"
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 
 #include "TestInputListener.h"
 
@@ -45,7 +44,7 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
     static constexpr nsecs_t downTime = 2;
     NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2,
-                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::LogicalDisplayId::DEFAULT,
                                 /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
                                 /*flags=*/0, AMETA_NONE, /*buttonState=*/0,
                                 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
@@ -81,7 +80,7 @@
 TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) {
     // Create a basic key event and send to processor
     NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3,
-                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0,
+                       AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/0,
                        AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
                        AMETA_NONE, /*downTime=*/6);
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index e0a3e94..8536ff0 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -27,6 +27,7 @@
 #include <JoystickInputMapper.h>
 #include <KeyboardInputMapper.h>
 #include <MultiTouchInputMapper.h>
+#include <NotifyArgsBuilders.h>
 #include <PeripheralController.h>
 #include <SensorInputMapper.h>
 #include <SingleTouchInputMapper.h>
@@ -40,13 +41,11 @@
 #include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <ui/Rotation.h>
 
 #include <thread>
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
-#include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InstrumentedInputReader.h"
 #include "TestConstants.h"
@@ -58,15 +57,16 @@
 using namespace ftl::flag_operators;
 using testing::AllOf;
 using std::chrono_literals::operator""ms;
+using std::chrono_literals::operator""s;
 
 // Arbitrary display properties.
-static constexpr int32_t DISPLAY_ID = 0;
+static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 static const std::string DISPLAY_UNIQUE_ID = "local:1";
-static constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1;
-static const std::string SECONDARY_DISPLAY_UNIQUE_ID = "local:2";
+static constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID =
+        ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
 static constexpr int32_t DISPLAY_WIDTH = 480;
 static constexpr int32_t DISPLAY_HEIGHT = 800;
-static constexpr int32_t VIRTUAL_DISPLAY_ID = 1;
+static constexpr ui::LogicalDisplayId VIRTUAL_DISPLAY_ID = ui::LogicalDisplayId{1};
 static constexpr int32_t VIRTUAL_DISPLAY_WIDTH = 400;
 static constexpr int32_t VIRTUAL_DISPLAY_HEIGHT = 500;
 static const char* VIRTUAL_DISPLAY_UNIQUE_ID = "virtual:1";
@@ -97,8 +97,6 @@
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
 static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
-// Maximum smoothing time delta so that we don't generate events too far into the future.
-constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32);
 
 namespace input_flags = com::android::input::flags;
 
@@ -151,7 +149,7 @@
     std::istringstream iss(dump);
     for (std::string line; std::getline(iss, line);) {
         ALOGE("%s", line.c_str());
-        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        std::this_thread::sleep_for(1ms);
     }
 }
 
@@ -360,7 +358,7 @@
     virtual void fadePointer() {
     }
 
-    virtual std::optional<int32_t> getAssociatedDisplay() {
+    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplay() {
         if (mViewport) {
             return std::make_optional(mViewport->displayId);
         }
@@ -419,8 +417,8 @@
     const std::string externalUniqueId = "local:1";
     const std::string virtualUniqueId1 = "virtual:2";
     const std::string virtualUniqueId2 = "virtual:3";
-    constexpr int32_t virtualDisplayId1 = 2;
-    constexpr int32_t virtualDisplayId2 = 3;
+    constexpr ui::LogicalDisplayId virtualDisplayId1 = ui::LogicalDisplayId{2};
+    constexpr ui::LogicalDisplayId virtualDisplayId2 = ui::LogicalDisplayId{3};
 
     // Add an internal viewport
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
@@ -477,8 +475,8 @@
 TEST_F(InputReaderPolicyTest, Viewports_TwoOfSameType) {
     const std::string uniqueId1 = "uniqueId1";
     const std::string uniqueId2 = "uniqueId2";
-    constexpr int32_t displayId1 = 2;
-    constexpr int32_t displayId2 = 3;
+    constexpr ui::LogicalDisplayId displayId1 = ui::LogicalDisplayId{2};
+    constexpr ui::LogicalDisplayId displayId2 = ui::LogicalDisplayId{3};
 
     std::vector<ViewportType> types = {ViewportType::INTERNAL, ViewportType::EXTERNAL,
                                        ViewportType::VIRTUAL};
@@ -522,13 +520,13 @@
 TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) {
     const std::string uniqueId1 = "uniqueId1";
     const std::string uniqueId2 = "uniqueId2";
-    constexpr int32_t nonDefaultDisplayId = 2;
-    static_assert(nonDefaultDisplayId != ADISPLAY_ID_DEFAULT,
-                  "Test display ID should not be ADISPLAY_ID_DEFAULT");
+    constexpr ui::LogicalDisplayId nonDefaultDisplayId = ui::LogicalDisplayId{2};
+    ASSERT_NE(nonDefaultDisplayId, ui::LogicalDisplayId::DEFAULT)
+            << "Test display ID should not be ui::LogicalDisplayId::DEFAULT ";
 
     // Add the default display first and ensure it gets returned.
     mFakePolicy->clearViewports();
-    mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+    mFakePolicy->addDisplayViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
                                     ViewportType::INTERNAL);
     mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
@@ -538,7 +536,7 @@
     std::optional<DisplayViewport> viewport =
             mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
     ASSERT_TRUE(viewport);
-    ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, viewport->displayId);
     ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
 
     // Add the default display second to make sure order doesn't matter.
@@ -546,13 +544,13 @@
     mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
                                     ViewportType::INTERNAL);
-    mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+    mFakePolicy->addDisplayViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
                                     ViewportType::INTERNAL);
 
     viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
     ASSERT_TRUE(viewport);
-    ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, viewport->displayId);
     ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
 }
 
@@ -563,8 +561,8 @@
     constexpr ViewportType type = ViewportType::EXTERNAL;
     const std::string uniqueId1 = "uniqueId1";
     const std::string uniqueId2 = "uniqueId2";
-    constexpr int32_t displayId1 = 1;
-    constexpr int32_t displayId2 = 2;
+    constexpr ui::LogicalDisplayId displayId1 = ui::LogicalDisplayId{1};
+    constexpr ui::LogicalDisplayId displayId2 = ui::LogicalDisplayId{2};
     const uint8_t hdmi1 = 0;
     const uint8_t hdmi2 = 1;
     const uint8_t hdmi3 = 2;
@@ -1166,18 +1164,18 @@
 TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) {
     NotifyPointerCaptureChangedArgs args;
 
-    auto request = mFakePolicy->setPointerCapture(true);
+    auto request = mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
     mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasCalled(&args);
-    ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled.";
+    ASSERT_TRUE(args.request.isEnable()) << "Pointer Capture should be enabled.";
     ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match.";
 
-    mFakePolicy->setPointerCapture(false);
+    mFakePolicy->setPointerCapture(/*window=*/nullptr);
     mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasCalled(&args);
-    ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled.";
+    ASSERT_FALSE(args.request.isEnable()) << "Pointer Capture should be disabled.";
 
     // Verify that the Pointer Capture state is not updated when the configuration value
     // does not change.
@@ -1186,6 +1184,82 @@
     mFakeListener->assertNotifyCaptureWasNotCalled();
 }
 
+TEST_F(InputReaderTest, GetLastUsedInputDeviceId) {
+    constexpr int32_t FIRST_DEVICE_ID = END_RESERVED_ID + 1000;
+    constexpr int32_t SECOND_DEVICE_ID = FIRST_DEVICE_ID + 1;
+    FakeInputMapper& firstMapper =
+            addDeviceWithFakeInputMapper(FIRST_DEVICE_ID, FIRST_DEVICE_ID, "first",
+                                         InputDeviceClass::KEYBOARD, AINPUT_SOURCE_KEYBOARD,
+                                         /*configuration=*/nullptr);
+    FakeInputMapper& secondMapper =
+            addDeviceWithFakeInputMapper(SECOND_DEVICE_ID, SECOND_DEVICE_ID, "second",
+                                         InputDeviceClass::TOUCH_MT, AINPUT_SOURCE_STYLUS,
+                                         /*configuration=*/nullptr);
+
+    ASSERT_EQ(ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // Start a new key gesture from the first device
+    firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                                          .deviceId(FIRST_DEVICE_ID)
+                                          .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(firstMapper.getDeviceId(), mReader->getLastUsedInputDeviceId());
+
+    // Start a new touch gesture from the second device
+    secondMapper.setProcessResult(
+            {MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                     .deviceId(SECOND_DEVICE_ID)
+                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER))
+                     .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // Releasing the key is not a new gesture, so it does not update the last used device
+    firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+                                          .deviceId(FIRST_DEVICE_ID)
+                                          .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // But pressing a new key does start a new gesture
+    firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                                          .deviceId(FIRST_DEVICE_ID)
+                                          .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // Moving or ending a touch gesture does not update the last used device
+    secondMapper.setProcessResult(
+            {MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                     .deviceId(SECOND_DEVICE_ID)
+                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS))
+                     .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+    secondMapper.setProcessResult({MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_STYLUS)
+                                           .deviceId(SECOND_DEVICE_ID)
+                                           .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS))
+                                           .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // Starting a new hover gesture updates the last used device
+    secondMapper.setProcessResult(
+            {MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                     .deviceId(SECOND_DEVICE_ID)
+                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS))
+                     .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+}
+
 class FakeVibratorInputMapper : public FakeInputMapper {
 public:
     FakeVibratorInputMapper(InputDeviceContext& deviceContext,
@@ -1350,15 +1424,14 @@
     sp<FakeInputReaderPolicy> mFakePolicy;
     std::unique_ptr<InputReaderInterface> mReader;
 
-    std::shared_ptr<FakePointerController> mFakePointerController;
+    constexpr static auto EVENT_HAPPENED_TIMEOUT = 2000ms;
+    constexpr static auto EVENT_DID_NOT_HAPPEN_TIMEOUT = 30ms;
 
     void SetUp() override {
 #if !defined(__ANDROID__)
         GTEST_SKIP();
 #endif
         mFakePolicy = sp<FakeInputReaderPolicy>::make();
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
 
         setupInputReader();
     }
@@ -1373,18 +1446,28 @@
         mFakePolicy.clear();
     }
 
-    std::optional<InputDeviceInfo> findDeviceByName(const std::string& name) {
-        const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices();
-        const auto& it = std::find_if(inputDevices.begin(), inputDevices.end(),
-                                      [&name](const InputDeviceInfo& info) {
-                                          return info.getIdentifier().name == name;
-                                      });
-        return it != inputDevices.end() ? std::make_optional(*it) : std::nullopt;
+    std::optional<InputDeviceInfo> waitForDevice(const std::string& deviceName) {
+        std::chrono::time_point start = std::chrono::steady_clock::now();
+        while (true) {
+            const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices();
+            const auto& it = std::find_if(inputDevices.begin(), inputDevices.end(),
+                                          [&deviceName](const InputDeviceInfo& info) {
+                                              return info.getIdentifier().name == deviceName;
+                                          });
+            if (it != inputDevices.end()) {
+                return std::make_optional(*it);
+            }
+            std::this_thread::sleep_for(1ms);
+            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
+            if (elapsed > 5s) {
+                return {};
+            }
+        }
     }
 
     void setupInputReader() {
-        mTestListener = std::make_unique<TestInputListener>(/*eventHappenedTimeout=*/2000ms,
-                                                            /*eventDidNotHappenTimeout=*/30ms);
+        mTestListener = std::make_unique<TestInputListener>(EVENT_HAPPENED_TIMEOUT,
+                                                            EVENT_DID_NOT_HAPPEN_TIMEOUT);
 
         mReader = std::make_unique<InputReader>(std::make_shared<EventHub>(), mFakePolicy,
                                                 *mTestListener);
@@ -1432,7 +1515,7 @@
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     ASSERT_EQ(initialNumDevices + 1, mFakePolicy->getInputDevices().size());
 
-    const auto device = findDeviceByName(keyboard->getName());
+    const auto device = waitForDevice(keyboard->getName());
     ASSERT_TRUE(device.has_value());
     ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, device->getKeyboardType());
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources());
@@ -1475,7 +1558,7 @@
     std::unique_ptr<UinputExternalStylus> stylus = createUinputDevice<UinputExternalStylus>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
 
-    const auto device = findDeviceByName(stylus->getName());
+    const auto device = waitForDevice(stylus->getName());
     ASSERT_TRUE(device.has_value());
 
     // An external stylus with buttons should also be recognized as a keyboard.
@@ -1515,7 +1598,7 @@
                                                                           BTN_STYLUS3});
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
 
-    const auto device = findDeviceByName(keyboard->getName());
+    const auto device = waitForDevice(keyboard->getName());
     ASSERT_TRUE(device.has_value());
 
     // An alphabetical keyboard that reports stylus buttons should not be recognized as a stylus.
@@ -1532,7 +1615,7 @@
                     std::initializer_list<int>{KEY_VOLUMEUP, KEY_VOLUMEDOWN});
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
 
-    const auto device = findDeviceByName(keyboard->getName());
+    const auto device = waitForDevice(keyboard->getName());
     ASSERT_TRUE(device.has_value());
 
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources())
@@ -1586,12 +1669,12 @@
         mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
         ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
-        const auto info = findDeviceByName(mDevice->getName());
+        const auto info = waitForDevice(mDevice->getName());
         ASSERT_TRUE(info);
         mDeviceInfo = *info;
     }
 
-    void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
+    void setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
                                       ui::Rotation orientation, const std::string& uniqueId,
                                       std::optional<uint8_t> physicalPort,
                                       ViewportType viewportType) {
@@ -1642,8 +1725,6 @@
         } else {
             mFakePolicy->addInputUniqueIdAssociation(INPUT_PORT, UNIQUE_ID);
         }
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
 
         InputReaderIntegrationTest::setupInputReader();
 
@@ -1657,7 +1738,7 @@
                                      ViewportType::INTERNAL);
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
         ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
-        const auto info = findDeviceByName(mDevice->getName());
+        const auto info = waitForDevice(mDevice->getName());
         ASSERT_TRUE(info);
         mDeviceInfo = *info;
     }
@@ -1990,7 +2071,7 @@
     auto externalStylus = createUinputDevice<UinputExternalStylus>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
-    const auto stylusInfo = findDeviceByName(externalStylus->getName());
+    const auto stylusInfo = waitForDevice(externalStylus->getName());
     ASSERT_TRUE(stylusInfo);
 
     // Move
@@ -2061,7 +2142,7 @@
         mStylus = mStylusDeviceLifecycleTracker.get();
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
         ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
-        const auto info = findDeviceByName(mStylus->getName());
+        const auto info = waitForDevice(mStylus->getName());
         ASSERT_TRUE(info);
         mStylusInfo = *info;
     }
@@ -2331,11 +2412,11 @@
             createUinputDevice<UinputExternalStylusWithPressure>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
-    const auto stylusInfo = findDeviceByName(stylus->getName());
+    const auto stylusInfo = waitForDevice(stylus->getName());
     ASSERT_TRUE(stylusInfo);
 
     // Connecting an external stylus changes the source of the touchscreen.
-    const auto deviceInfo = findDeviceByName(mDevice->getName());
+    const auto deviceInfo = waitForDevice(mDevice->getName());
     ASSERT_TRUE(deviceInfo);
     ASSERT_TRUE(isFromSource(deviceInfo->getSources(), STYLUS_FUSION_SOURCE));
 }
@@ -2349,7 +2430,7 @@
             createUinputDevice<UinputExternalStylusWithPressure>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
-    const auto stylusInfo = findDeviceByName(stylus->getName());
+    const auto stylusInfo = waitForDevice(stylus->getName());
     ASSERT_TRUE(stylusInfo);
 
     ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
@@ -2395,7 +2476,7 @@
             createUinputDevice<UinputExternalStylusWithPressure>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
-    const auto stylusInfo = findDeviceByName(stylus->getName());
+    const auto stylusInfo = waitForDevice(stylus->getName());
     ASSERT_TRUE(stylusInfo);
 
     ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
@@ -2415,17 +2496,29 @@
     mDevice->sendTrackingId(FIRST_TRACKING_ID);
     mDevice->sendToolType(MT_TOOL_FINGER);
     mDevice->sendDown(centerPoint);
-    auto waitUntil = std::chrono::system_clock::now() +
-            std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT));
+    const auto syncTime = std::chrono::system_clock::now();
+    // After 72 ms, the event *will* be generated. If we wait the full 72 ms to check that NO event
+    // is generated in that period, there will be a race condition between the event being generated
+    // and the test's wait timeout expiring. Thus, we wait for a shorter duration in the test, which
+    // will reduce the liklihood of the race condition occurring.
+    const auto waitUntilTimeForNoEvent =
+            syncTime + std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT / 2));
     mDevice->sendSync();
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntil));
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntilTimeForNoEvent));
 
     // Since the external stylus did not report a pressure value within the timeout,
     // it shows up as a finger pointer.
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
-                  WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), WithPressure(1.f))));
+    const auto waitUntilTimeForEvent = syncTime +
+            std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT)) + EVENT_HAPPENED_TIMEOUT;
+    ASSERT_NO_FATAL_FAILURE(
+            mTestListener->assertNotifyMotionWasCalled(AllOf(WithMotionAction(
+                                                                     AMOTION_EVENT_ACTION_DOWN),
+                                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN |
+                                                                        AINPUT_SOURCE_STYLUS),
+                                                             WithToolType(ToolType::FINGER),
+                                                             WithDeviceId(touchscreenId),
+                                                             WithPressure(1.f)),
+                                                       waitUntilTimeForEvent));
 
     // Change the pressure on the external stylus. Since the pressure was not present at the start
     // of the gesture, it is ignored for now.
@@ -2463,7 +2556,7 @@
     std::unique_ptr<UinputExternalStylus> stylus = createUinputDevice<UinputExternalStylus>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
-    const auto stylusInfo = findDeviceByName(stylus->getName());
+    const auto stylusInfo = waitForDevice(stylus->getName());
     ASSERT_TRUE(stylusInfo);
 
     ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
@@ -2693,6 +2786,31 @@
     ASSERT_NO_FATAL_FAILURE(mapper2.assertProcessWasCalled());
 }
 
+TEST_F(InputDeviceTest, Configure_SmoothScrollViewBehaviorNotSet) {
+    // Set some behavior to force the configuration to be update.
+    mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "1");
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                        AINPUT_SOURCE_KEYBOARD);
+
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
+
+    ASSERT_FALSE(mDevice->getDeviceInfo().getViewBehavior().shouldSmoothScroll.has_value());
+}
+
+TEST_F(InputDeviceTest, Configure_SmoothScrollViewBehaviorEnabled) {
+    mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.viewBehavior_smoothScroll", "1");
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                        AINPUT_SOURCE_KEYBOARD);
+
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
+
+    ASSERT_TRUE(mDevice->getDeviceInfo().getViewBehavior().shouldSmoothScroll.value_or(false));
+}
+
 TEST_F(InputDeviceTest, WakeDevice_AddsWakeFlagToProcessNotifyArgs) {
     mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "1");
     FakeInputMapper& mapper =
@@ -2855,9 +2973,12 @@
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
                                     NO_PORT, ViewportType::INTERNAL);
+    const auto initialGeneration = mDevice->getGeneration();
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
+    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueIdByPort());
+    ASSERT_GT(mDevice->getGeneration(), initialGeneration);
+    ASSERT_EQ(mDevice->getDeviceInfo().getAssociatedDisplayId(), SECONDARY_DISPLAY_ID);
 }
 
 /**
@@ -2894,9 +3015,9 @@
     mapper.assertConfigureWasCalled();
     mapper.assertResetWasNotCalled();
 
-    RawEvent event{.deviceId = EVENTHUB_ID,
+    RawEvent event{.when = ARBITRARY_TIME,
                    .readTime = ARBITRARY_TIME,
-                   .when = ARBITRARY_TIME,
+                   .deviceId = EVENTHUB_ID,
                    .type = EV_SYN,
                    .code = SYN_REPORT,
                    .value = 0};
@@ -2906,13 +3027,11 @@
     mapper.assertProcessWasCalled();
 
     // Simulate a kernel buffer overflow, which generates a SYN_DROPPED event.
-    // This should reset the mapper.
     event.type = EV_SYN;
     event.code = SYN_DROPPED;
     event.value = 0;
     unused = mDevice->process(&event, /*count=*/1);
     mapper.assertProcessWasNotCalled();
-    mapper.assertResetWasCalled();
 
     // All events until the next SYN_REPORT should be dropped.
     event.type = EV_KEY;
@@ -2922,11 +3041,13 @@
     mapper.assertProcessWasNotCalled();
 
     // We get the SYN_REPORT event now, which is not forwarded to mappers.
+    // This should reset the mapper.
     event.type = EV_SYN;
     event.code = SYN_REPORT;
     event.value = 0;
     unused = mDevice->process(&event, /*count=*/1);
     mapper.assertProcessWasNotCalled();
+    mapper.assertResetWasCalled();
 
     // The mapper receives events normally now.
     event.type = EV_KEY;
@@ -3199,7 +3320,7 @@
 
     void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
                              int32_t originalKeyCode, int32_t rotatedKeyCode,
-                             int32_t displayId = ADISPLAY_ID_NONE);
+                             ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID);
 };
 
 /* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the
@@ -3212,7 +3333,8 @@
 
 void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper,
                                                   int32_t originalScanCode, int32_t originalKeyCode,
-                                                  int32_t rotatedKeyCode, int32_t displayId) {
+                                                  int32_t rotatedKeyCode,
+                                                  ui::LogicalDisplayId displayId) {
     NotifyKeyArgs args;
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 1);
@@ -3533,19 +3655,19 @@
                                                        AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     NotifyKeyArgs args;
 
-    // Display id should be ADISPLAY_ID_NONE without any display configuration.
+    // Display id should be LogicalDisplayId::INVALID without any display configuration.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, args.displayId);
 
     prepareDisplay(ui::ROTATION_0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, args.displayId);
 }
 
 TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) {
@@ -3559,7 +3681,7 @@
                                                        AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     NotifyKeyArgs args;
 
-    // Display id should be ADISPLAY_ID_NONE without any display configuration.
+    // Display id should be LogicalDisplayId::INVALID without any display configuration.
     // ^--- already checked by the previous test
 
     setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
@@ -3570,7 +3692,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DISPLAY_ID, args.displayId);
 
-    constexpr int32_t newDisplayId = 2;
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
     clearViewports();
     setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                  UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
@@ -3785,7 +3907,7 @@
     ASSERT_FALSE(device2->isEnabled());
 
     // Prepare second display.
-    constexpr int32_t newDisplayId = 2;
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
     setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                  UNIQUE_ID, hdmi1, ViewportType::INTERNAL);
     setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
@@ -4087,7 +4209,7 @@
     void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); }
 };
 
-TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) {
+TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_AlphabeticKeyboard) {
     // For external devices, keys will trigger wake on key down. Media keys should also trigger
     // wake if triggered from external devices.
 
@@ -4126,6 +4248,36 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
+TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_NoneAlphabeticKeyboard) {
+    // For external devices, keys will trigger wake on key down. Media keys should not trigger
+    // wake if triggered from external non-alphaebtic keyboard (e.g. headsets).
+
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE,
+                          POLICY_FLAG_WAKE);
+
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
+    NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+}
+
 TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) {
     // Tv Remote key's wake behavior is prescribed by the keylayout file.
 
@@ -4164,1492 +4316,6 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
-// --- CursorInputMapperTestBase ---
-
-class CursorInputMapperTestBase : public InputMapperTest {
-protected:
-    static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
-
-    std::shared_ptr<FakePointerController> mFakePointerController;
-
-    void SetUp() override {
-        InputMapperTest::SetUp();
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
-    }
-
-    void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY,
-                            int32_t rotatedX, int32_t rotatedY);
-
-    void prepareDisplay(ui::Rotation orientation) {
-        setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation,
-                                     DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    }
-
-    void prepareSecondaryDisplay() {
-        setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                     ui::ROTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT,
-                                     ViewportType::EXTERNAL);
-    }
-
-    static void assertCursorPointerCoords(const PointerCoords& coords, float x, float y,
-                                          float pressure) {
-        ASSERT_NO_FATAL_FAILURE(assertPointerCoords(coords, x, y, pressure, 0.0f, 0.0f, 0.0f, 0.0f,
-                                                    0.0f, 0.0f, 0.0f, EPSILON));
-    }
-};
-
-const int32_t CursorInputMapperTestBase::TRACKBALL_MOVEMENT_THRESHOLD = 6;
-
-void CursorInputMapperTestBase::testMotionRotation(CursorInputMapper& mapper, int32_t originalX,
-                                                   int32_t originalY, int32_t rotatedX,
-                                                   int32_t rotatedY) {
-    NotifyMotionArgs args;
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, originalY);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(args.pointerCoords[0],
-                                      float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
-                                      float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
-}
-
-// --- CursorInputMapperTest ---
-
-class CursorInputMapperTest : public CursorInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(false);
-        CursorInputMapperTestBase::SetUp();
-    }
-};
-
-TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources());
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    InputDeviceInfo info;
-    mapper.populateDeviceInfo(info);
-
-    // Initially there may not be a valid motion range.
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-
-    // When the bounds are set, then there should be a valid motion range.
-    mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    InputDeviceInfo info2;
-    mapper.populateDeviceInfo(info2);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE,
-            1, 800 - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE,
-            2, 480 - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE,
-            0.0f, 1.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    InputDeviceInfo info;
-    mapper.populateDeviceInfo(info);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
-            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL,
-            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL,
-            0.0f, 1.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
-
-    NotifyMotionArgs args;
-
-    // Button press.
-    // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    // Button release.  Should have same down time.
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(0, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(0, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Motion in X but not Y.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f,
-                                                      0.0f));
-
-    // Motion in Y but not X.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Button press.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    // Button release.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Combined X, Y and Button.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    // Move X, Y a bit while pressed.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    // Release Button.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotions) {
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
-    addConfigurationProperty("cursor.mode", "navigation");
-    // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not
-    // need to be rotated.
-    addConfigurationProperty("cursor.orientationAware", "1");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    prepareDisplay(ui::ROTATION_90);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
-}
-
-TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotions) {
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
-    addConfigurationProperty("cursor.mode", "navigation");
-    // Since InputReader works in the un-rotated coordinate space, only devices that are not
-    // orientation-aware are affected by display rotation.
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_0);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_90);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1, -1));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_180);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1, -1));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_270);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1,  1));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs motionArgs;
-    NotifyKeyArgs keyArgs;
-
-    // press BTN_LEFT, release BTN_LEFT
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    // press BTN_BACK, release BTN_BACK
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_SIDE, release BTN_SIDE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_FORWARD, release BTN_FORWARD
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    // press BTN_EXTRA, release BTN_EXTRA
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-}
-
-TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs args;
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-/**
- * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
- * pointer acceleration or speed processing should not be applied.
- */
-TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
-                                               /*high threshold=*/100.f, /*acceleration=*/10.f);
-    mFakePolicy->setVelocityControlParams(testParams);
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    NotifyMotionArgs args;
-
-    // Move and verify scale is applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_GT(relX, 10);
-    ASSERT_GT(relY, 20);
-
-    // Enable Pointer Capture
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    NotifyPointerCaptureChangedArgs captureArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
-    ASSERT_TRUE(captureArgs.request.enable);
-
-    // Move and verify scale is not applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_EQ(10, args.pointerCoords[0].getX());
-    ASSERT_EQ(20, args.pointerCoords[0].getY());
-}
-
-TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    // Ensure the display is rotated.
-    prepareDisplay(ui::ROTATION_90);
-
-    NotifyMotionArgs args;
-
-    // Verify that the coordinates are rotated.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_EQ(-20, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X));
-    ASSERT_EQ(10, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
-
-    // Enable Pointer Capture.
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    NotifyPointerCaptureChangedArgs captureArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
-    ASSERT_TRUE(captureArgs.request.enable);
-
-    // Move and verify rotation is not applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_EQ(10, args.pointerCoords[0].getX());
-    ASSERT_EQ(20, args.pointerCoords[0].getY());
-}
-
-TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_90);
-
-    // Set up the secondary display as the display on which the pointer should be shown.
-    // The InputDevice is not associated with any display.
-    prepareSecondaryDisplay();
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    // Ensure input events are generated for the secondary display.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
-                  WithCoords(110.0f, 220.0f))));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_90);
-
-    // Set up the secondary display as the display on which the pointer should be shown,
-    // and associate the InputDevice with the secondary display.
-    prepareSecondaryDisplay();
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
-                  WithCoords(110.0f, 220.0f))));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display as the display on which the pointer should be shown.
-    prepareDisplay(ui::ROTATION_90);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-
-    // Associate the InputDevice with the secondary display.
-    prepareSecondaryDisplay();
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    // The mapper should not generate any events because it is associated with a display that is
-    // different from the pointer display.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-}
-
-// --- CursorInputMapperTestWithChoreographer ---
-
-// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
-//   logic can be removed.
-class CursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(true);
-        CursorInputMapperTestBase::SetUp();
-    }
-};
-
-TEST_F(CursorInputMapperTestWithChoreographer, PopulateDeviceInfoReturnsRangeFromPolicy) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    InputDeviceInfo info;
-    mapper.populateDeviceInfo(info);
-
-    // Initially there may not be a valid motion range.
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
-                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-
-    // When the viewport and the default pointer display ID is set, then there should be a valid
-    // motion range.
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    InputDeviceInfo info2;
-    mapper.populateDeviceInfo(info2);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0,
-                                              DISPLAY_WIDTH - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0,
-                                              DISPLAY_HEIGHT - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
-                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, ProcessShouldHandleAllButtonsWithZeroCoords) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    prepareDisplay(ui::ROTATION_0);
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs motionArgs;
-    NotifyKeyArgs keyArgs;
-
-    // press BTN_LEFT, release BTN_LEFT
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    // press BTN_BACK, release BTN_BACK
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_SIDE, release BTN_SIDE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_FORWARD, release BTN_FORWARD
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    // press BTN_EXTRA, release BTN_EXTRA
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, ProcessWhenModeIsPointerShouldKeepZeroCoords) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    prepareDisplay(ui::ROTATION_0);
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs args;
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                                                0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
-                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
-    mFakePolicy->setVelocityControlParams(testParams);
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    prepareDisplay(ui::ROTATION_0);
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    NotifyMotionArgs args;
-
-    // Move and verify scale is applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_GT(relX, 10);
-    ASSERT_GT(relY, 20);
-
-    // Enable Pointer Capture
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    NotifyPointerCaptureChangedArgs captureArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
-    ASSERT_TRUE(captureArgs.request.enable);
-
-    // Move and verify scale is not applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    const float relX2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_EQ(10, relX2);
-    ASSERT_EQ(20, relY2);
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdNoAssociatedViewport) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_90);
-
-    // Set up the secondary display as the display on which the pointer should be shown.
-    // The InputDevice is not associated with any display.
-    prepareSecondaryDisplay();
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    // Ensure input events are generated without display ID and coords,
-    // because they will be decided later by PointerChoreographer.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE),
-                  WithCoords(0.0f, 0.0f))));
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdWithAssociatedViewport) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_90);
-
-    // Set up the secondary display as the display on which the pointer should be shown,
-    // and associate the InputDevice with the secondary display.
-    prepareSecondaryDisplay();
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    // Ensure input events are generated with associated display ID but not with coords,
-    // because the coords will be decided later by PointerChoreographer.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
-                  WithCoords(0.0f, 0.0f))));
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer,
-       ConfigureDisplayIdShouldGenerateEventWithMismatchedPointerDisplay) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display as the display on which the pointer should be shown.
-    prepareDisplay(ui::ROTATION_90);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-
-    // Associate the InputDevice with the secondary display.
-    prepareSecondaryDisplay();
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    // With PointerChoreographer enabled, there could be a PointerController for the associated
-    // display even if it is different from the pointer display. So the mapper should generate an
-    // event.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
-                  WithCoords(0.0f, 0.0f))));
-}
-
-// --- BluetoothCursorInputMapperTest ---
-
-class BluetoothCursorInputMapperTest : public CursorInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(false);
-        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
-    }
-};
-
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events that come in quick succession, according to their timestamps.
-    for (int i = 0; i < 3; i++) {
-        constexpr static nsecs_t delta = ms2ns(1);
-        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
-        kernelEventTime += delta;
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events with the same timestamp from the kernel.
-    // Ensure that we do not generate events too far into the future.
-    constexpr static int32_t numEvents =
-            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
-    for (int i = 0; i < numEvents; i++) {
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-
-    // By processing more events with the same timestamp, we should not generate events with a
-    // timestamp that is more than the specified max time delta from the timestamp at its injection.
-    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
-    for (int i = 0; i < 3; i++) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(cappedEventTime))));
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
-    // smoothening is not needed, its timestamp is not affected.
-    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
-    expectedEventTime = kernelEventTime;
-
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-}
-
-// --- BluetoothCursorInputMapperTestWithChoreographer ---
-
-class BluetoothCursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(true);
-        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
-    }
-};
-
-TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmoothening) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_0);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events that come in quick succession, according to their timestamps.
-    for (int i = 0; i < 3; i++) {
-        constexpr static nsecs_t delta = ms2ns(1);
-        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
-        kernelEventTime += delta;
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningIsCapped) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_0);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events with the same timestamp from the kernel.
-    // Ensure that we do not generate events too far into the future.
-    constexpr static int32_t numEvents =
-            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
-    for (int i = 0; i < numEvents; i++) {
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-
-    // By processing more events with the same timestamp, we should not generate events with a
-    // timestamp that is more than the specified max time delta from the timestamp at its injection.
-    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
-    for (int i = 0; i < 3; i++) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(cappedEventTime))));
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningNotUsed) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_0);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
-    // smoothening is not needed, its timestamp is not affected.
-    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
-    expectedEventTime = kernelEventTime;
-
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-}
-
 // --- TouchInputMapperTest ---
 
 class TouchInputMapperTest : public InputMapperTest {
@@ -6748,10 +5414,6 @@
 }
 
 TEST_F(SingleTouchInputMapperTest, Process_DoesntCheckPhysicalFrameForTouchpads) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(fakePointerController);
-
     addConfigurationProperty("touch.deviceType", "pointer");
     prepareAxes(POSITION);
     prepareDisplay(ui::ROTATION_0);
@@ -7596,52 +6258,6 @@
     ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
 }
 
-TEST_F(SingleTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareButtons();
-    prepareAxes(POSITION);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setStylusPointerIconEnabled(true);
-    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
-
-    processKey(mapper, BTN_TOOL_PEN, 1);
-    processMove(mapper, 100, 200);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(ToolType::STYLUS),
-                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
-    ASSERT_TRUE(fakePointerController->isPointerShown());
-    ASSERT_NO_FATAL_FAILURE(
-            fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200)));
-}
-
-TEST_F(SingleTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareButtons();
-    prepareAxes(POSITION);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setStylusPointerIconEnabled(false);
-    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
-
-    processKey(mapper, BTN_TOOL_PEN, 1);
-    processMove(mapper, 100, 200);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(ToolType::STYLUS),
-                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
-    ASSERT_FALSE(fakePointerController->isPointerShown());
-}
-
 TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) {
     // Initialize the device without setting device source to touch navigation.
     addConfigurationProperty("touch.deviceType", "touchScreen");
@@ -10121,21 +8737,12 @@
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) {
-    // Setup for second display.
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    fakePointerController->setPosition(100, 200);
-    mFakePolicy->setPointerController(fakePointerController);
-
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
     prepareSecondaryDisplay(ViewportType::EXTERNAL);
 
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
-    // Check source is mouse that would obtain the PointerController.
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
 
     NotifyMotionArgs motionArgs;
@@ -10144,7 +8751,7 @@
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, motionArgs.displayId);
 }
 
 /**
@@ -10324,97 +8931,6 @@
             WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
 }
 
-TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) {
-    // Setup the first touch screen device.
-    prepareAxes(POSITION | ID | SLOT);
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    // Create the second touch screen device, and enable multi fingers.
-    const std::string USB2 = "USB2";
-    const std::string DEVICE_NAME2 = "TOUCHSCREEN2";
-    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
-    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
-    std::shared_ptr<InputDevice> device2 =
-            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
-                      ftl::Flags<InputDeviceClass>(0));
-
-    mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX,
-                                   /*flat=*/0, /*fuzz=*/0);
-    mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX,
-                                   /*flat=*/0, /*fuzz=*/0);
-    mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_TRACKING_ID, RAW_ID_MIN, RAW_ID_MAX,
-                                   /*flat=*/0, /*fuzz=*/0);
-    mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_SLOT, RAW_SLOT_MIN, RAW_SLOT_MAX,
-                                   /*flat=*/0, /*fuzz=*/0);
-    mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, /*value=*/0);
-    mFakeEventHub->addConfigurationProperty(SECOND_EVENTHUB_ID, String8("touch.deviceType"),
-                                            String8("touchScreen"));
-
-    // Setup the second touch screen device.
-    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
-    MultiTouchInputMapper& mapper2 = device2->constructAndAddMapper<
-            MultiTouchInputMapper>(SECOND_EVENTHUB_ID, mFakePolicy->getReaderConfiguration());
-    std::list<NotifyArgs> unused =
-            device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/{});
-    unused += device2->reset(ARBITRARY_TIME);
-
-    // Setup PointerController.
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(fakePointerController);
-
-    // Setup policy for associated displays and show touches.
-    const uint8_t hdmi1 = 0;
-    const uint8_t hdmi2 = 1;
-    mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1);
-    mFakePolicy->addInputPortAssociation(USB2, hdmi2);
-    mFakePolicy->setShowTouches(true);
-
-    // Create displays.
-    prepareDisplay(ui::ROTATION_0, hdmi1);
-    prepareSecondaryDisplay(ViewportType::EXTERNAL, hdmi2);
-
-    // Default device will reconfigure above, need additional reconfiguration for another device.
-    unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::Change::DISPLAY_INFO |
-                                         InputReaderConfiguration::Change::SHOW_TOUCHES);
-
-    // Two fingers down at default display.
-    int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
-    processPosition(mapper, x1, y1);
-    processId(mapper, 1);
-    processSlot(mapper, 1);
-    processPosition(mapper, x2, y2);
-    processId(mapper, 2);
-    processSync(mapper);
-
-    std::map<int32_t, std::vector<int32_t>>::const_iterator iter =
-            fakePointerController->getSpots().find(DISPLAY_ID);
-    ASSERT_TRUE(iter != fakePointerController->getSpots().end());
-    ASSERT_EQ(size_t(2), iter->second.size());
-
-    // Two fingers down at second display.
-    processPosition(mapper2, x1, y1);
-    processId(mapper2, 1);
-    processSlot(mapper2, 1);
-    processPosition(mapper2, x2, y2);
-    processId(mapper2, 2);
-    processSync(mapper2);
-
-    iter = fakePointerController->getSpots().find(SECONDARY_DISPLAY_ID);
-    ASSERT_TRUE(iter != fakePointerController->getSpots().end());
-    ASSERT_EQ(size_t(2), iter->second.size());
-
-    // Disable the show touches configuration and ensure the spots are cleared.
-    mFakePolicy->setShowTouches(false);
-    unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::Change::SHOW_TOUCHES);
-
-    ASSERT_TRUE(fakePointerController->getSpots().empty());
-}
-
 TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) {
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
@@ -10450,7 +8966,7 @@
 
     // Test all 4 orientations
     for (ui::Rotation orientation : ftl::enum_range<ui::Rotation>()) {
-        SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
+        SCOPED_TRACE(StringPrintf("Orientation %s", ftl::enum_string(orientation).c_str()));
         clearViewports();
         prepareDisplay(orientation);
         std::vector<TouchVideoFrame> frames{frame};
@@ -10475,7 +8991,7 @@
 
     // Test all 4 orientations
     for (ui::Rotation orientation : ftl::enum_range<ui::Rotation>()) {
-        SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
+        SCOPED_TRACE(StringPrintf("Orientation %s", ftl::enum_string(orientation).c_str()));
         clearViewports();
         prepareDisplay(orientation);
         std::vector<TouchVideoFrame> frames{frame};
@@ -10971,15 +9487,16 @@
     ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
 }
 
-TEST_F(MultiTouchInputMapperTest, ResetClearsTouchState) {
+TEST_F(MultiTouchInputMapperTest, Reset_RepopulatesMultiTouchState) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // First finger down.
+    constexpr int32_t x1 = 100, y1 = 200, x2 = 300, y2 = 400;
     processId(mapper, FIRST_TRACKING_ID);
-    processPosition(mapper, 100, 200);
+    processPosition(mapper, x1, y1);
     processPressure(mapper, RAW_PRESSURE_MAX);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
@@ -10988,42 +9505,49 @@
     // Second finger down.
     processSlot(mapper, SECOND_SLOT);
     processId(mapper, SECOND_TRACKING_ID);
-    processPosition(mapper, 300, 400);
+    processPosition(mapper, x2, y2);
     processPressure(mapper, RAW_PRESSURE_MAX);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(
             mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN)));
 
-    // Reset the mapper. When the mapper is reset, the touch state is also cleared.
+    // Set MT Slot state to be repopulated for the required slots
+    std::vector<int32_t> mtSlotValues(RAW_SLOT_MAX + 1, -1);
+    mtSlotValues[0] = FIRST_TRACKING_ID;
+    mtSlotValues[1] = SECOND_TRACKING_ID;
+    mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_TRACKING_ID, mtSlotValues);
+
+    mtSlotValues[0] = x1;
+    mtSlotValues[1] = x2;
+    mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_POSITION_X, mtSlotValues);
+
+    mtSlotValues[0] = y1;
+    mtSlotValues[1] = y2;
+    mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_POSITION_Y, mtSlotValues);
+
+    mtSlotValues[0] = RAW_PRESSURE_MAX;
+    mtSlotValues[1] = RAW_PRESSURE_MAX;
+    mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_PRESSURE, mtSlotValues);
+
+    // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be
+    // repopulated. Resetting should cancel the ongoing gesture.
     resetMapper(mapper, ARBITRARY_TIME);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
 
-    // Move the second slot pointer, and ensure there are no events, because the touch state was
-    // cleared and no slots should be in use.
+    // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
+    // the existing touch state to generate a down event.
     processPosition(mapper, 301, 302);
     processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-
-    // Release both fingers.
-    processId(mapper, INVALID_TRACKING_ID);
-    processSlot(mapper, FIRST_SLOT);
-    processId(mapper, INVALID_TRACKING_ID);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-
-    // Start a new gesture, and ensure we get a DOWN event for it.
-    processId(mapper, FIRST_TRACKING_ID);
-    processPosition(mapper, 200, 300);
-    processPressure(mapper, RAW_PRESSURE_MAX);
-    processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(ACTION_POINTER_1_DOWN), WithPressure(1.f))));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
-TEST_F(MultiTouchInputMapperTest, ResetClearsTouchStateWithNoPointersDown) {
+TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
@@ -11099,118 +9623,6 @@
                   WithToolType(ToolType::STYLUS))));
 }
 
-TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE);
-    // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only
-    // indicate stylus presence dynamically.
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setStylusPointerIconEnabled(true);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    processId(mapper, FIRST_TRACKING_ID);
-    processPressure(mapper, RAW_PRESSURE_MIN);
-    processPosition(mapper, 100, 200);
-    processToolType(mapper, MT_TOOL_PEN);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(ToolType::STYLUS),
-                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
-    ASSERT_TRUE(fakePointerController->isPointerShown());
-    ASSERT_NO_FATAL_FAILURE(
-            fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200)));
-}
-
-TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE);
-    // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only
-    // indicate stylus presence dynamically.
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setStylusPointerIconEnabled(false);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    processId(mapper, FIRST_TRACKING_ID);
-    processPressure(mapper, RAW_PRESSURE_MIN);
-    processPosition(mapper, 100, 200);
-    processToolType(mapper, MT_TOOL_PEN);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(ToolType::STYLUS),
-                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
-    ASSERT_FALSE(fakePointerController->isPointerShown());
-}
-
-TEST_F(MultiTouchInputMapperTest, SimulateKernelBufferOverflow) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT | PRESSURE);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    // First finger down.
-    processId(mapper, FIRST_TRACKING_ID);
-    processPosition(mapper, 100, 200);
-    processPressure(mapper, RAW_PRESSURE_MAX);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
-
-    // Assume the kernel buffer overflows, and we get a SYN_DROPPED event.
-    // This will reset the mapper, and thus also reset the touch state.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_DROPPED, 0);
-    resetMapper(mapper, ARBITRARY_TIME);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
-
-    // Since the touch state was reset, it doesn't know which slots are active, so any movements
-    // are ignored.
-    processPosition(mapper, 101, 201);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-
-    // Second finger goes down. This is the first active finger, so we get a DOWN event.
-    processSlot(mapper, SECOND_SLOT);
-    processId(mapper, SECOND_TRACKING_ID);
-    processPosition(mapper, 400, 500);
-    processPressure(mapper, RAW_PRESSURE_MAX);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
-
-    // First slot is still ignored, only the second one is active.
-    processSlot(mapper, FIRST_SLOT);
-    processPosition(mapper, 102, 202);
-    processSlot(mapper, SECOND_SLOT);
-    processPosition(mapper, 401, 501);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
-
-    // Both slots up, so we get the UP event for the active pointer.
-    processSlot(mapper, FIRST_SLOT);
-    processId(mapper, INVALID_TRACKING_ID);
-    processSlot(mapper, SECOND_SLOT);
-    processId(mapper, INVALID_TRACKING_ID);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(
-            mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-}
-
 // --- MultiTouchInputMapperTest_ExternalDevice ---
 
 class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
@@ -11236,7 +9648,7 @@
     processPosition(mapper, 100, 100);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(ADISPLAY_ID_DEFAULT, motionArgs.displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, motionArgs.displayId);
 
     // Expect the event to be sent to the external viewport if it is present.
     prepareSecondaryDisplay(ViewportType::EXTERNAL);
@@ -11246,168 +9658,15 @@
     ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
 }
 
-TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
-    // we need a pointer controller for mouse mode of touchpad (start pointer at 0,0)
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    fakePointerController->setPosition(0, 0);
-
-    // prepare device and capture
+// TODO(b/281840344): Remove the test when the old touchpad stack is removed. It is currently
+//  unclear what the behavior of the touchpad logic in TouchInputMapper should do after the
+//  PointerChoreographer refactor.
+TEST_F(MultiTouchInputMapperTest, DISABLED_Process_TouchpadPointer) {
+    // prepare device
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerCapture(true);
-    mFakePolicy->setPointerController(fakePointerController);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    // captured touchpad should be a touchpad source
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
-
-    InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
-
-    const InputDeviceInfo::MotionRange* relRangeX =
-            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD);
-    ASSERT_NE(relRangeX, nullptr);
-    ASSERT_EQ(relRangeX->min, -(RAW_X_MAX - RAW_X_MIN));
-    ASSERT_EQ(relRangeX->max, RAW_X_MAX - RAW_X_MIN);
-    const InputDeviceInfo::MotionRange* relRangeY =
-            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD);
-    ASSERT_NE(relRangeY, nullptr);
-    ASSERT_EQ(relRangeY->min, -(RAW_Y_MAX - RAW_Y_MIN));
-    ASSERT_EQ(relRangeY->max, RAW_Y_MAX - RAW_Y_MIN);
-
-    // run captured pointer tests - note that this is unscaled, so input listener events should be
-    //                              identical to what the hardware sends (accounting for any
-    //                              calibration).
-    // FINGER 0 DOWN
-    processSlot(mapper, 0);
-    processId(mapper, 1);
-    processPosition(mapper, 100 + RAW_X_MIN, 100 + RAW_Y_MIN);
-    processKey(mapper, BTN_TOUCH, 1);
-    processSync(mapper);
-
-    // expect coord[0] to contain initial location of touch 0
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(1U, args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, args.source);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 DOWN
-    processSlot(mapper, 1);
-    processId(mapper, 2);
-    processPosition(mapper, 560 + RAW_X_MIN, 154 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
-    ASSERT_EQ(2U, args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(1, args.pointerProperties[1].id);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 560, 154, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 MOVE
-    processPosition(mapper, 540 + RAW_X_MIN, 690 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
-    // from move
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 0 MOVE
-    processSlot(mapper, 0);
-    processPosition(mapper, 50 + RAW_X_MIN, 800 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain new touch 0 location, coord[1] to contain previous location
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 50, 800, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // BUTTON DOWN
-    processKey(mapper, BTN_LEFT, 1);
-    processSync(mapper);
-
-    // touchinputmapper design sends a move before button press
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-
-    // BUTTON UP
-    processKey(mapper, BTN_LEFT, 0);
-    processSync(mapper);
-
-    // touchinputmapper design sends a move after button release
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-
-    // FINGER 0 UP
-    processId(mapper, -1);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | 0x0000, args.action);
-
-    // FINGER 1 MOVE
-    processSlot(mapper, 1);
-    processPosition(mapper, 320 + RAW_X_MIN, 900 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain new location of touch 1, and properties[0].id to contain 1
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_EQ(1U, args.getPointerCount());
-    ASSERT_EQ(1, args.pointerProperties[0].id);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 320, 900, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 UP
-    processId(mapper, -1);
-    processKey(mapper, BTN_TOUCH, 0);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-
-    // non captured touchpad should be a mouse source
-    mFakePolicy->setPointerCapture(false);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-}
-
-TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    fakePointerController->setPosition(0, 0);
-
-    // prepare device and capture
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(fakePointerController);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     // run uncaptured pointer tests - pushes out generic events
     // FINGER 0 DOWN
@@ -11460,24 +9719,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
 }
 
-TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-
+TEST_F(MultiTouchInputMapperTest, Touchpad_GetSources) {
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setPointerCapture(false);
+    mFakePolicy->setPointerCapture(/*window=*/nullptr);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // uncaptured touchpad should be a pointer device
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-
-    // captured touchpad should be a touchpad device
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 // --- BluetoothMultiTouchInputMapperTest ---
@@ -11536,19 +9786,14 @@
     float mPointerXZoomScale;
     void preparePointerMode(int xAxisResolution, int yAxisResolution) {
         addConfigurationProperty("touch.deviceType", "pointer");
-        std::shared_ptr<FakePointerController> fakePointerController =
-                std::make_shared<FakePointerController>();
-        fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-        fakePointerController->setPosition(0, 0);
         prepareDisplay(ui::ROTATION_0);
 
         prepareAxes(POSITION);
         prepareAbsoluteAxisResolution(xAxisResolution, yAxisResolution);
         // In order to enable swipe and freeform gesture in pointer mode, pointer capture
         // needs to be disabled, and the pointer gesture needs to be enabled.
-        mFakePolicy->setPointerCapture(false);
+        mFakePolicy->setPointerCapture(/*window=*/nullptr);
         mFakePolicy->setPointerGestureEnabled(true);
-        mFakePolicy->setPointerController(fakePointerController);
 
         float rawDiagonal = hypotf(RAW_X_MAX - RAW_X_MIN, RAW_Y_MAX - RAW_Y_MIN);
         float displayDiagonal = hypotf(DISPLAY_WIDTH, DISPLAY_HEIGHT);
@@ -12054,6 +10299,28 @@
     ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_BRIGHTNESS);
 }
 
+TEST_F(LightControllerTest, MonoKeyboardMuteLight) {
+    RawLightInfo infoMono = {.id = 1,
+                             .name = "mono_keyboard_mute",
+                             .maxBrightness = 255,
+                             .flags = InputLightClass::BRIGHTNESS |
+                                     InputLightClass::KEYBOARD_MIC_MUTE,
+                             .path = ""};
+    mFakeEventHub->addRawLightInfo(infoMono.id, std::move(infoMono));
+
+    PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
+
+    InputDeviceInfo info;
+    controller.populateDeviceInfo(&info);
+    std::vector<InputDeviceLightInfo> lights = info.getLights();
+    ASSERT_EQ(1U, lights.size());
+    ASSERT_EQ(InputDeviceLightType::KEYBOARD_MIC_MUTE, lights[0].type);
+    ASSERT_EQ(0U, lights[0].preferredBrightnessLevels.size());
+}
+
 TEST_F(LightControllerTest, MonoKeyboardBacklight) {
     RawLightInfo infoMono = {.id = 1,
                              .name = "mono_keyboard_backlight",
diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp
new file mode 100644
index 0000000..32acb5f
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2024 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 "InputTraceSession.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/PrintTools.h>
+
+#include <utility>
+
+namespace android {
+
+using perfetto::protos::pbzero::AndroidInputEvent;
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+using perfetto::protos::pbzero::AndroidKeyEvent;
+using perfetto::protos::pbzero::AndroidMotionEvent;
+using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+
+// These operator<< definitions must be in the global namespace for them to be accessible to the
+// GTEST library. They cannot be in the anonymous namespace.
+static std::ostream& operator<<(std::ostream& out,
+                                const std::variant<KeyEvent, MotionEvent>& event) {
+    std::visit([&](const auto& e) { out << e; }, event);
+    return out;
+}
+
+static std::ostream& operator<<(std::ostream& out,
+                                const InputTraceSession::WindowDispatchEvent& event) {
+    out << "Window dispatch to windowId: " << event.window->getId() << ", event: " << event.event;
+    return out;
+}
+
+namespace {
+
+inline uint32_t getId(const std::variant<KeyEvent, MotionEvent>& event) {
+    return std::visit([&](const auto& e) { return e.getId(); }, event);
+}
+
+std::unique_ptr<perfetto::TracingSession> startTrace(
+        const std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)>& configure) {
+    protozero::HeapBuffered<AndroidInputEventConfig> inputEventConfig{};
+    configure(inputEventConfig);
+
+    perfetto::TraceConfig config;
+    config.add_buffers()->set_size_kb(1024); // Record up to 1 MiB.
+    auto* dataSourceConfig = config.add_data_sources()->mutable_config();
+    dataSourceConfig->set_name("android.input.inputevent");
+    dataSourceConfig->set_android_input_event_config_raw(inputEventConfig.SerializeAsString());
+
+    std::unique_ptr<perfetto::TracingSession> tracingSession(perfetto::Tracing::NewTrace());
+    tracingSession->Setup(config);
+    tracingSession->StartBlocking();
+    return tracingSession;
+}
+
+std::string stopTrace(std::unique_ptr<perfetto::TracingSession> tracingSession) {
+    tracingSession->StopBlocking();
+    std::vector<char> traceChars(tracingSession->ReadTraceBlocking());
+    return {traceChars.data(), traceChars.size()};
+}
+
+// Decodes the trace, and returns all of the traced input events, and whether they were each
+// traced as a redacted event.
+auto decodeTrace(const std::string& rawTrace) {
+    using namespace perfetto::protos::pbzero;
+
+    ArrayMap<AndroidMotionEvent::Decoder, bool /*redacted*/> tracedMotions;
+    ArrayMap<AndroidKeyEvent::Decoder, bool /*redacted*/> tracedKeys;
+    ArrayMap<AndroidWindowInputDispatchEvent::Decoder, bool /*redacted*/> tracedWindowDispatches;
+
+    Trace::Decoder trace{rawTrace};
+    if (trace.has_packet()) {
+        auto it = trace.packet();
+        while (it) {
+            TracePacket::Decoder packet{it->as_bytes()};
+            if (packet.has_android_input_event()) {
+                AndroidInputEvent::Decoder event{packet.android_input_event()};
+                if (event.has_dispatcher_motion_event()) {
+                    tracedMotions.emplace_back(event.dispatcher_motion_event(),
+                                               /*redacted=*/false);
+                }
+                if (event.has_dispatcher_motion_event_redacted()) {
+                    tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(),
+                                               /*redacted=*/true);
+                }
+                if (event.has_dispatcher_key_event()) {
+                    tracedKeys.emplace_back(event.dispatcher_key_event(),
+                                            /*redacted=*/false);
+                }
+                if (event.has_dispatcher_key_event_redacted()) {
+                    tracedKeys.emplace_back(event.dispatcher_key_event_redacted(),
+                                            /*redacted=*/true);
+                }
+                if (event.has_dispatcher_window_dispatch_event()) {
+                    tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(),
+                                                        /*redacted=*/false);
+                }
+                if (event.has_dispatcher_window_dispatch_event_redacted()) {
+                    tracedWindowDispatches
+                            .emplace_back(event.dispatcher_window_dispatch_event_redacted(),
+                                          /*redacted=*/true);
+                }
+            }
+            it++;
+        }
+    }
+    return std::tuple{std::move(tracedMotions), std::move(tracedKeys),
+                      std::move(tracedWindowDispatches)};
+}
+
+bool eventMatches(const MotionEvent& expected, const AndroidMotionEvent::Decoder& traced) {
+    return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const KeyEvent& expected, const AndroidKeyEvent::Decoder& traced) {
+    return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const InputTraceSession::WindowDispatchEvent& expected,
+                  const AndroidWindowInputDispatchEvent::Decoder& traced) {
+    return static_cast<uint32_t>(getId(expected.event)) == traced.event_id() &&
+            expected.window->getId() == traced.window_id();
+}
+
+template <typename ExpectedEvents, typename TracedEvents>
+void verifyExpectedEventsTraced(const ExpectedEvents& expectedEvents,
+                                const TracedEvents& tracedEvents, std::string_view name) {
+    uint32_t totalExpectedCount = 0;
+
+    for (const auto& [expectedEvent, expectedLevel] : expectedEvents) {
+        int32_t totalMatchCount = 0;
+        int32_t redactedMatchCount = 0;
+        for (const auto& [tracedEvent, isRedacted] : tracedEvents) {
+            if (eventMatches(expectedEvent, tracedEvent)) {
+                totalMatchCount++;
+                if (isRedacted) {
+                    redactedMatchCount++;
+                }
+            }
+        }
+        switch (expectedLevel) {
+            case Level::NONE:
+                ASSERT_EQ(totalMatchCount, 0) << "Event should not be traced, but it was traced"
+                                              << "\n\tExpected event: " << expectedEvent;
+                break;
+            case Level::REDACTED:
+            case Level::COMPLETE:
+                ASSERT_EQ(totalMatchCount, 1)
+                        << "Event should match exactly one traced event, but it matched: "
+                        << totalMatchCount << "\n\tExpected event: " << expectedEvent;
+                ASSERT_EQ(redactedMatchCount, expectedLevel == Level::REDACTED ? 1 : 0);
+                totalExpectedCount++;
+                break;
+        }
+    }
+
+    ASSERT_EQ(tracedEvents.size(), totalExpectedCount)
+            << "The number of traced " << name
+            << " events does not exactly match the number of expected events";
+}
+
+} // namespace
+
+InputTraceSession::InputTraceSession(
+        std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)> configure)
+      : mPerfettoSession(startTrace(std::move(configure))) {}
+
+InputTraceSession::~InputTraceSession() {
+    const auto rawTrace = stopTrace(std::move(mPerfettoSession));
+    verifyExpectations(rawTrace);
+}
+
+void InputTraceSession::expectMotionTraced(Level level, const MotionEvent& event) {
+    mExpectedMotions.emplace_back(event, level);
+}
+
+void InputTraceSession::expectKeyTraced(Level level, const KeyEvent& event) {
+    mExpectedKeys.emplace_back(event, level);
+}
+
+void InputTraceSession::expectDispatchTraced(Level level, const WindowDispatchEvent& event) {
+    mExpectedWindowDispatches.emplace_back(event, level);
+}
+
+void InputTraceSession::verifyExpectations(const std::string& rawTrace) {
+    auto [tracedMotions, tracedKeys, tracedWindowDispatches] = decodeTrace(rawTrace);
+
+    verifyExpectedEventsTraced(mExpectedMotions, tracedMotions, "motion");
+    verifyExpectedEventsTraced(mExpectedKeys, tracedKeys, "key");
+    verifyExpectedEventsTraced(mExpectedWindowDispatches, tracedWindowDispatches,
+                               "window dispatch");
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h
new file mode 100644
index 0000000..ed20bc8
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 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 "FakeWindows.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <perfetto/tracing.h>
+#include <variant>
+#include <vector>
+
+namespace android {
+
+/**
+ * Tracing level constants used for adding expectations to the InputTraceSession.
+ */
+enum class Level {
+    NONE,
+    REDACTED,
+    COMPLETE,
+};
+
+template <typename K, typename V>
+using ArrayMap = std::vector<std::pair<K, V>>;
+
+/**
+ * A scoped representation of a tracing session that is used to make assertions on the trace.
+ *
+ * When the trace session is created, an "android.input.inputevent" trace will be started
+ * synchronously with the given configuration. While the trace is ongoing, the caller must
+ * specify the events that are expected to be in the trace using the expect* methods.
+ *
+ * When the session is destroyed, the trace is stopped synchronously, and all expectations will
+ * be verified using the gtest framework. This acts as a strict verifier, where the verification
+ * will fail both if an expected event does not show up in the trace and if there is an extra
+ * event in the trace that was not expected. Ordering is NOT verified for any events.
+ */
+class InputTraceSession {
+public:
+    explicit InputTraceSession(
+            std::function<void(
+                    protozero::HeapBuffered<perfetto::protos::pbzero::AndroidInputEventConfig>&)>
+                    configure);
+
+    ~InputTraceSession();
+
+    void expectMotionTraced(Level level, const MotionEvent& event);
+
+    void expectKeyTraced(Level level, const KeyEvent& event);
+
+    struct WindowDispatchEvent {
+        std::variant<KeyEvent, MotionEvent> event;
+        sp<FakeWindowHandle> window;
+    };
+    void expectDispatchTraced(Level level, const WindowDispatchEvent& event);
+
+private:
+    std::unique_ptr<perfetto::TracingSession> mPerfettoSession;
+    ArrayMap<WindowDispatchEvent, Level> mExpectedWindowDispatches;
+    ArrayMap<MotionEvent, Level> mExpectedMotions;
+    ArrayMap<KeyEvent, Level> mExpectedKeys;
+
+    void verifyExpectations(const std::string& rawTrace);
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp
new file mode 100644
index 0000000..617d67f
--- /dev/null
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -0,0 +1,749 @@
+/*
+ * Copyright 2024 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 "../InputCommonConverter.h"
+#include "../dispatcher/InputDispatcher.h"
+#include "../dispatcher/trace/InputTracingPerfettoBackend.h"
+#include "../dispatcher/trace/ThreadedBackend.h"
+#include "FakeApplicationHandle.h"
+#include "FakeInputDispatcherPolicy.h"
+#include "FakeWindows.h"
+#include "InputTraceSession.h"
+#include "TestEventMatchers.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <android/content/pm/IPackageManagerNative.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <private/android_filesystem_config.h>
+#include <map>
+#include <vector>
+
+namespace android::inputdispatcher::trace {
+
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+
+namespace {
+
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+
+// Ensure common actions are interchangeable between keys and motions for convenience.
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_DOWN) ==
+              static_cast<int32_t>(AKEY_EVENT_ACTION_DOWN));
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_UP) ==
+              static_cast<int32_t>(AKEY_EVENT_ACTION_UP));
+constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP;
+constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+
+constexpr gui::Pid PID{1};
+
+constexpr gui::Uid ALLOWED_UID_1{10012};
+constexpr gui::Uid ALLOWED_UID_2{10013};
+constexpr gui::Uid DISALLOWED_UID_1{1};
+constexpr gui::Uid DISALLOWED_UID_2{99};
+constexpr gui::Uid UNLISTED_UID{12345};
+
+const std::string ALLOWED_PKG_1{"allowed.pkg.1"};
+const std::string ALLOWED_PKG_2{"allowed.pkg.2"};
+const std::string DISALLOWED_PKG_1{"disallowed.pkg.1"};
+const std::string DISALLOWED_PKG_2{"disallowed.pkg.2"};
+
+const std::map<std::string, gui::Uid> kPackageUidMap{
+        {ALLOWED_PKG_1, ALLOWED_UID_1},
+        {ALLOWED_PKG_2, ALLOWED_UID_2},
+        {DISALLOWED_PKG_1, DISALLOWED_UID_1},
+        {DISALLOWED_PKG_2, DISALLOWED_UID_2},
+};
+
+class FakePackageManager : public content::pm::IPackageManagerNativeDefault {
+public:
+    binder::Status getPackageUid(const ::std::string& pkg, int64_t flags, int32_t userId,
+            int32_t* outUid) override {
+        auto it = kPackageUidMap.find(pkg);
+        *outUid = it != kPackageUidMap.end() ? static_cast<int32_t>(it->second.val()) : -1;
+        return binder::Status::ok();
+    }
+};
+
+const sp<testing::NiceMock<FakePackageManager>> kPackageManager =
+        sp<testing::NiceMock<FakePackageManager>>::make();
+
+const std::shared_ptr<FakeApplicationHandle> APP = std::make_shared<FakeApplicationHandle>();
+
+} // namespace
+
+// --- InputTracingTest ---
+
+class InputTracingTest : public testing::Test {
+protected:
+    std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
+    std::unique_ptr<InputDispatcher> mDispatcher;
+
+    void SetUp() override {
+        impl::PerfettoBackend::sUseInProcessBackendForTest = true;
+        impl::PerfettoBackend::sPackageManagerProvider = []() { return kPackageManager; };
+        mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
+
+        auto tracingBackend = std::make_unique<impl::ThreadedBackend<impl::PerfettoBackend>>(
+                impl::PerfettoBackend());
+        mRequestTracerIdle = tracingBackend->getIdleWaiterForTesting();
+        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, std::move(tracingBackend));
+
+        mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+        ASSERT_EQ(OK, mDispatcher->start());
+    }
+
+    void TearDown() override {
+        ASSERT_EQ(OK, mDispatcher->stop());
+        mDispatcher.reset();
+        mFakePolicy.reset();
+    }
+
+    void waitForTracerIdle() {
+        mDispatcher->waitForIdle();
+        mRequestTracerIdle();
+    }
+
+    void setFocusedWindow(const sp<gui::WindowInfoHandle>& window) {
+        gui::FocusRequest request;
+        request.token = window->getToken();
+        request.windowName = window->getName();
+        request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+        request.displayId = window->getInfo()->displayId.val();
+        mDispatcher->setFocusedWindow(request);
+    }
+
+    void tapAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+                      Level inboundTraceLevel, Level dispatchTraceLevel, InputTraceSession& s) {
+        const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                  .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                                  .build();
+        mDispatcher->notifyMotion(down);
+        s.expectMotionTraced(inboundTraceLevel, toMotionEvent(down));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+
+        const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                                .build();
+        mDispatcher->notifyMotion(up);
+        s.expectMotionTraced(inboundTraceLevel, toMotionEvent(up));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+    }
+
+    void keypressAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+                           Level inboundTraceLevel, Level dispatchTraceLevel,
+                           InputTraceSession& s) {
+        const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build();
+        mDispatcher->notifyKey(down);
+        s.expectKeyTraced(inboundTraceLevel, toKeyEvent(down));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_DOWN));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+
+        const auto up = KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build();
+        mDispatcher->notifyKey(up);
+        s.expectKeyTraced(inboundTraceLevel, toKeyEvent(up));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_UP));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+    }
+
+private:
+    std::function<void()> mRequestTracerIdle;
+};
+
+TEST_F(InputTracingTest, EmptyConfigTracesNothing) {
+    InputTraceSession s{[](auto& config) {}};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceAll) {
+    InputTraceSession s{
+            [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, NoRulesTracesNothing) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, EmptyRuleMatchesEverything) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match everything as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, UnspecifiedTracelLevel) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match everything, trace level unspecified
+        auto rule = config->add_rules();
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    // Event is not traced by default if trace level is unspecified
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchSecureWindow) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match secure windows as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_secure(true);
+    }};
+
+    // Add a normal window and a spy window.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Since neither are secure windows, events should not be traced.
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    // Events should be matched as secure if any of the target windows is marked as secure.
+    spy->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(false);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(true);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(false);
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchImeConnectionActive) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match IME Connection Active as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_ime_connection_active(true);
+    }};
+
+    // Add a normal window and a spy window.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Since IME connection is not active, events should not be traced.
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAllPackages) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match all package as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_all_packages(ALLOWED_PKG_1);
+        rule->add_match_all_packages(ALLOWED_PKG_2);
+    }};
+
+    // All windows are allowlisted.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_2);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    auto systemSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    systemSpy->setOwnerInfo(PID, gui::Uid{AID_SYSTEM});
+    systemSpy->setSpy(true);
+    systemSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged(
+            {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Add a disallowed spy. This will result in the event not being traced for all windows.
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+                                        *disallowedSpy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Change the owner of the disallowed spy to one for which we don't have a package mapping.
+    disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+    mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+                                        *disallowedSpy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Remove the disallowed spy. Events are traced again.
+    mDispatcher->onWindowInfosChanged(
+            {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAnyPackages) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match any package as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_any_packages(ALLOWED_PKG_1);
+        rule->add_match_any_packages(ALLOWED_PKG_2);
+    }};
+
+    // Just a disallowed window. Events are not traced.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, DISALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // Add a spy for which we don't have a package mapping. Events are still not traced.
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Add an allowed spy. Events are now traced for all packages.
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_1);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged(
+            {{*disallowedSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Add another disallowed spy. Events are still traced.
+    auto disallowedSpy2 = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy2->setOwnerInfo(PID, DISALLOWED_UID_2);
+    disallowedSpy2->setSpy(true);
+    disallowedSpy2->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *disallowedSpy2->getInfo(),
+                                        *spy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({disallowedSpy, disallowedSpy2, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleMatchersInOneRule) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match all of the following conditions as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_all_packages(ALLOWED_PKG_1);
+        rule->add_match_all_packages(ALLOWED_PKG_2);
+        rule->add_match_any_packages(ALLOWED_PKG_1);
+        rule->add_match_any_packages(DISALLOWED_PKG_1);
+        rule->set_match_secure(false);
+        rule->set_match_ime_connection_active(false);
+    }};
+
+    // A single window into an allowed UID. Matches all matchers.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Secure window does not match.
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // IME Connection Active does not match.
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    mDispatcher->setInputMethodConnectionIsActive(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // Event going to DISALLOWED_PKG_1 does not match because it's not listed in match_all_packages.
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Event going to ALLOWED_PKG_1 does not match because it's not listed in match_any_packages.
+    window->setOwnerInfo(PID, ALLOWED_UID_2);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // All conditions match.
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_1);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleRulesMatchInOrder) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Don't trace secure events
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_NONE);
+        rule1->set_match_secure(true);
+        // Rule: Trace matched packages as COMPLETE when IME inactive
+        auto rule2 = config->add_rules();
+        rule2->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule2->add_match_all_packages(ALLOWED_PKG_1);
+        rule2->add_match_all_packages(ALLOWED_PKG_2);
+        rule2->set_match_ime_connection_active(false);
+        // Rule: Trace the rest of the events as REDACTED
+        auto rule3 = config->add_rules();
+        rule3->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Verify that the first rule that matches in the order that they are specified is the
+    // one that applies to the event.
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    tapAndExpect({window}, Level::REDACTED, Level::REDACTED, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_2);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    spy->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    spy->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::REDACTED, Level::REDACTED, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceInboundEvents) {
+    InputTraceSession s{[](auto& config) {
+        // Only trace inbounds events - don't trace window dispatch
+        config->set_trace_dispatcher_input_events(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace everything as REDACTED
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // Only the inbound events are traced. No dispatch events are traced.
+    tapAndExpect({window}, Level::REDACTED, Level::NONE, s);
+
+    // Notify a down event, which should be traced.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    s.expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+    mDispatcher->notifyMotion(down);
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    // Force a cancel event to be synthesized. This should not be traced, because only inbound
+    // events are requested.
+    mDispatcher->cancelCurrentTouch();
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    s.expectMotionTraced(Level::NONE, *consumed);
+    s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceWindowDispatch) {
+    InputTraceSession s{[](auto& config) {
+        // Only trace window dispatch - don't trace event details
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace everything as REDACTED
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // Only dispatch events are traced. No inbound events are traced.
+    tapAndExpect({window}, Level::NONE, Level::REDACTED, s);
+
+    // Notify a down event; the dispatch should be traced.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    s.expectMotionTraced(Level::NONE, toMotionEvent(down));
+    mDispatcher->notifyMotion(down);
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+    // Force a cancel event to be synthesized. All events that are dispatched should be traced.
+    mDispatcher->cancelCurrentTouch();
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    s.expectMotionTraced(Level::NONE, *consumed);
+    s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+    waitForTracerIdle();
+}
+
+// TODO(b/336097719): Investigate flakiness and re-enable this test.
+TEST_F(InputTracingTest, DISABLED_SimultaneousTracingSessions) {
+    auto s1 = std::make_unique<InputTraceSession>(
+            [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); });
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+    auto s2 = std::make_unique<InputTraceSession>([](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace all events as REDACTED when IME inactive
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+        rule->set_match_ime_connection_active(false);
+    });
+
+    auto s3 = std::make_unique<InputTraceSession>([](auto& config) {
+        // Only trace window dispatch
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace non-secure events as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_secure(false);
+    });
+
+    // Down event should be recorded on all traces.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    mDispatcher->notifyMotion(down);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(down));
+    s2->expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(down));
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    // Move event when IME is active.
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    const auto move1 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                               .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                               .build();
+    mDispatcher->notifyMotion(move1);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move1));
+    s2->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::NONE, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    // Move event after window became secure.
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    const auto move2 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                               .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                               .build();
+    mDispatcher->notifyMotion(move2);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move2));
+    s2->expectMotionTraced(Level::REDACTED, toMotionEvent(move2));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(move2));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+    s3->expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    waitForTracerIdle();
+    s2.reset();
+
+    // Up event.
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                            .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                            .build();
+    mDispatcher->notifyMotion(up);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(up));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(up));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    waitForTracerIdle();
+    s3.reset();
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+    waitForTracerIdle();
+    s1.reset();
+}
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 7394913..6389cdc 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -28,7 +28,6 @@
 #include <EventHub.h>
 #include <InputReaderBase.h>
 #include <NotifyArgs.h>
-#include <PointerControllerInterface.h>
 #include <StylusState.h>
 #include <VibrationElement.h>
 #include <android-base/logging.h>
@@ -48,16 +47,12 @@
 class MockInputReaderContext : public InputReaderContext {
 public:
     MOCK_METHOD(void, updateGlobalMetaState, (), (override));
-    int32_t getGlobalMetaState() override { return 0; };
+    MOCK_METHOD(int32_t, getGlobalMetaState, (), (override));
 
     MOCK_METHOD(void, disableVirtualKeysUntil, (nsecs_t time), (override));
     MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode),
                 (override));
 
-    MOCK_METHOD(void, fadePointer, (), (override));
-    MOCK_METHOD(std::shared_ptr<PointerControllerInterface>, getPointerController,
-                (int32_t deviceId), (override));
-
     MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override));
     int32_t bumpGeneration() override { return ++mGeneration; }
 
@@ -132,6 +127,9 @@
 
     MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue),
                 (const, override));
+    MOCK_METHOD(base::Result<std::vector<int32_t>>, getMtSlotValues,
+                (int32_t deviceId, int32_t axis, size_t slotCount), (const, override));
+
     MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t deviceId, int32_t locationKeyCode),
                 (const, override));
     MOCK_METHOD(bool, markSupportedKeyCodes,
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 2ef7999..031b77d 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -55,6 +55,7 @@
 
     void SetUp() override {
         InputMapperUnitTest::SetUp();
+        createDevice();
 
         // set key-codes expected in tests
         for (const auto& [scanCode, outKeycode] : mKeyCodeMap) {
@@ -71,8 +72,6 @@
     }
 
     void testPointerVisibilityForKeys(const std::vector<int32_t>& keyCodes, bool expectVisible) {
-        EXPECT_CALL(mMockInputReaderContext, fadePointer)
-                .Times(expectVisible ? 0 : keyCodes.size());
         for (int32_t keyCode : keyCodes) {
             process(EV_KEY, keyCode, 1);
             process(EV_SYN, SYN_REPORT, 0);
@@ -83,7 +82,6 @@
 
     void testTouchpadTapStateForKeys(const std::vector<int32_t>& keyCodes,
                                      const bool expectPrevent) {
-        EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).Times(keyCodes.size());
         if (expectPrevent) {
             EXPECT_CALL(mMockInputReaderContext, setPreventingTouchpadTaps(true))
                     .Times(keyCodes.size());
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index 6606de8..4fcffdd 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -20,7 +20,6 @@
 #include <android-base/properties.h>
 #include <binder/Binder.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <inttypes.h>
 #include <linux/input.h>
 #include <log/log.h>
@@ -44,7 +43,7 @@
     identifier.product = productId;
     auto info = InputDeviceInfo();
     info.initialize(deviceId, /*generation=*/1, /*controllerNumber=*/1, identifier, "Test Device",
-                    /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+                    /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID);
     return info;
 }
 
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
new file mode 100644
index 0000000..b5f8971
--- /dev/null
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2024 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 "MultiTouchInputMapper.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <list>
+#include <optional>
+
+#include "InputMapperTest.h"
+#include "InterfaceMocks.h"
+#include "TestEventMatchers.h"
+
+#define TAG "MultiTouchpadInputMapperUnit_test"
+
+namespace android {
+
+using testing::_;
+using testing::IsEmpty;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::VariantWith;
+
+static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+static constexpr int32_t DISPLAY_WIDTH = 480;
+static constexpr int32_t DISPLAY_HEIGHT = 800;
+static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+static constexpr int32_t SLOT_COUNT = 5;
+
+static constexpr int32_t ACTION_POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t ACTION_POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+/**
+ * Unit tests for MultiTouchInputMapper.
+ */
+class MultiTouchInputMapperUnitTest : public InputMapperUnitTest {
+protected:
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+
+        // Present scan codes
+        expectScanCodes(/*present=*/true,
+                        {BTN_TOUCH, BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP,
+                         BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP});
+
+        // Missing scan codes that the mapper checks for.
+        expectScanCodes(/*present=*/false,
+                        {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL,
+                         BTN_TOOL_AIRBRUSH});
+
+        // Current scan code state - all keys are UP by default
+        setScanCodeState(KeyState::UP, {BTN_LEFT,           BTN_RIGHT,        BTN_MIDDLE,
+                                        BTN_BACK,           BTN_SIDE,         BTN_FORWARD,
+                                        BTN_EXTRA,          BTN_TASK,         BTN_TOUCH,
+                                        BTN_STYLUS,         BTN_STYLUS2,      BTN_0,
+                                        BTN_TOOL_FINGER,    BTN_TOOL_PEN,     BTN_TOOL_RUBBER,
+                                        BTN_TOOL_BRUSH,     BTN_TOOL_PENCIL,  BTN_TOOL_AIRBRUSH,
+                                        BTN_TOOL_MOUSE,     BTN_TOOL_LENS,    BTN_TOOL_DOUBLETAP,
+                                        BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP});
+
+        setKeyCodeState(KeyState::UP,
+                        {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY});
+
+        // Input properties - only INPUT_PROP_DIRECT for touchscreen
+        EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, _)).WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT))
+                .WillRepeatedly(Return(true));
+
+        // Axes that the device has
+        setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/SLOT_COUNT - 1, /*resolution=*/0);
+        setupAxis(ABS_MT_TRACKING_ID, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0);
+        setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24);
+        setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24);
+
+        // Axes that the device does not have
+        setupAxis(ABS_MT_PRESSURE, /*valid=*/false, /*min*/ 0, /*max=*/255, /*resolution=*/0);
+        setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+
+        // reset current slot at the beginning
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
+                .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) {
+                    *outValue = 0;
+                    return OK;
+                });
+
+        // mark all slots not in use
+        mockSlotValues({});
+
+        mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+        mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                        /*isActive=*/true, "local:0", NO_PORT,
+                                        ViewportType::INTERNAL);
+        createDevice();
+        mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
+                                                           mFakePolicy->getReaderConfiguration());
+    }
+
+    // Mocks position and tracking Ids for the provided slots. Remaining slots will be marked
+    // unused.
+    void mockSlotValues(
+            const std::unordered_map<int32_t /*slotIndex*/,
+                                     std::pair<Point /*position*/, int32_t /*trackingId*/>>&
+                    slotValues) {
+        EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, _, SLOT_COUNT))
+                .WillRepeatedly([=](int32_t, int32_t axis,
+                                    size_t slotCount) -> base::Result<std::vector<int32_t>> {
+                    // tracking Id for the unused slots must set to be < 0
+                    std::vector<int32_t> outMtSlotValues(slotCount + 1, -1);
+                    outMtSlotValues[0] = axis;
+                    switch (axis) {
+                        case ABS_MT_POSITION_X:
+                            for (const auto& [slotIndex, valuePair] : slotValues) {
+                                outMtSlotValues[slotIndex] = valuePair.first.x;
+                            }
+                            return outMtSlotValues;
+                        case ABS_MT_POSITION_Y:
+                            for (const auto& [slotIndex, valuePair] : slotValues) {
+                                outMtSlotValues[slotIndex] = valuePair.first.y;
+                            }
+                            return outMtSlotValues;
+                        case ABS_MT_TRACKING_ID:
+                            for (const auto& [slotIndex, valuePair] : slotValues) {
+                                outMtSlotValues[slotIndex] = valuePair.second;
+                            }
+                            return outMtSlotValues;
+                        default:
+                            return base::ResultError("Axis not supported", NAME_NOT_FOUND);
+                    }
+                });
+    }
+
+    std::list<NotifyArgs> processPosition(int32_t x, int32_t y) {
+        std::list<NotifyArgs> args;
+        args += process(EV_ABS, ABS_MT_POSITION_X, x);
+        args += process(EV_ABS, ABS_MT_POSITION_Y, y);
+        return args;
+    }
+
+    std::list<NotifyArgs> processId(int32_t id) { return process(EV_ABS, ABS_MT_TRACKING_ID, id); }
+
+    std::list<NotifyArgs> processKey(int32_t code, int32_t value) {
+        return process(EV_KEY, code, value);
+    }
+
+    std::list<NotifyArgs> processSlot(int32_t slot) { return process(EV_ABS, ABS_MT_SLOT, slot); }
+
+    std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); }
+};
+
+// This test simulates a multi-finger gesture with unexpected reset in between. This might happen
+// due to buffer overflow and device with report a SYN_DROPPED. In this case we expect mapper to be
+// reset, MT slot state to be re-populated and the gesture should be cancelled and restarted.
+TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) {
+    std::list<NotifyArgs> args;
+
+    // Two fingers down at once.
+    constexpr int32_t FIRST_TRACKING_ID = 1, SECOND_TRACKING_ID = 2;
+    int32_t x1 = 100, y1 = 125, x2 = 200, y2 = 225;
+    processKey(BTN_TOUCH, 1);
+    args += processPosition(x1, y1);
+    args += processId(FIRST_TRACKING_ID);
+    args += processSlot(1);
+    args += processPosition(x2, y2);
+    args += processId(SECOND_TRACKING_ID);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(ACTION_POINTER_1_DOWN))));
+
+    // Move.
+    x1 += 10;
+    y1 += 15;
+    x2 += 5;
+    y2 -= 10;
+    args = processSlot(0);
+    args += processPosition(x1, y1);
+    args += processSlot(1);
+    args += processPosition(x2, y2);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+    const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
+
+    // On buffer overflow mapper will be reset and MT slots data will be repopulated
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
+            .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) {
+                *outValue = 1;
+                return OK;
+            });
+
+    mockSlotValues(
+            {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
+
+    setScanCodeState(KeyState::DOWN, {BTN_TOUCH});
+
+    args = mMapper->reset(systemTime(SYSTEM_TIME_MONOTONIC));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))));
+
+    // SYN_REPORT should restart the gesture again
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(ACTION_POINTER_1_DOWN))));
+    ASSERT_EQ(std::get<NotifyMotionArgs>(args.back()).pointerCoords, pointerCoordsBeforeReset);
+
+    // Move.
+    x1 += 10;
+    y1 += 15;
+    x2 += 5;
+    y2 -= 10;
+    args = processSlot(0);
+    args += processPosition(x1, y1);
+    args += processSlot(1);
+    args += processPosition(x2, y2);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+
+    // First finger up.
+    args = processSlot(0);
+    args += processId(-1);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_POINTER_0_UP))));
+
+    // Second finger up.
+    processKey(BTN_TOUCH, 0);
+    args = processSlot(1);
+    args += processId(-1);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(
+                        VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
index 9fa6cdd..5e67506 100644
--- a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
+++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
@@ -23,6 +23,11 @@
 protected:
     static constexpr size_t SLOT_COUNT = 8;
 
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+        createDevice();
+    }
+
     MultiTouchMotionAccumulator mMotionAccumulator;
 
     void processMotionEvent(int32_t type, int32_t code, int32_t value) {
diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp
index 1536756..2e5ecc3 100644
--- a/services/inputflinger/tests/NotifyArgs_test.cpp
+++ b/services/inputflinger/tests/NotifyArgs_test.cpp
@@ -36,7 +36,7 @@
     nsecs_t readTime = downTime++;
     int32_t deviceId = 7;
     uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
-    int32_t displayId = 42;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId{42};
     uint32_t policyFlags = POLICY_FLAG_GESTURE;
     int32_t action = AMOTION_EVENT_ACTION_HOVER_MOVE;
     int32_t actionButton = AMOTION_EVENT_BUTTON_PRIMARY;
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 193b84d..1689b33 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -15,7 +15,8 @@
  */
 
 #include "../PointerChoreographer.h"
-
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gtest/gtest.h>
 #include <deque>
 #include <vector>
@@ -27,6 +28,8 @@
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 using ControllerType = PointerControllerInterface::ControllerType;
 using testing::AllOf;
 
@@ -42,10 +45,12 @@
 
 constexpr int32_t DEVICE_ID = 3;
 constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
-constexpr int32_t DISPLAY_ID = 5;
-constexpr int32_t ANOTHER_DISPLAY_ID = 10;
+constexpr int32_t THIRD_DEVICE_ID = SECOND_DEVICE_ID + 1;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId{5};
+constexpr ui::LogicalDisplayId ANOTHER_DISPLAY_ID = ui::LogicalDisplayId{10};
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr auto DRAWING_TABLET_SOURCE = AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS;
 
 const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE)
                                    .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
@@ -58,7 +63,7 @@
                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
 
 static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source,
-                                              int32_t associatedDisplayId) {
+                                              ui::LogicalDisplayId associatedDisplayId) {
     InputDeviceIdentifier identifier;
 
     auto info = InputDeviceInfo();
@@ -68,7 +73,7 @@
     return info;
 }
 
-static std::vector<DisplayViewport> createViewports(std::vector<int32_t> displayIds) {
+static std::vector<DisplayViewport> createViewports(std::vector<ui::LogicalDisplayId> displayIds) {
     std::vector<DisplayViewport> viewports;
     for (auto displayId : displayIds) {
         DisplayViewport viewport;
@@ -119,7 +124,7 @@
                                         "reference to this PointerController";
     }
 
-    void assertPointerDisplayIdNotified(int32_t displayId) {
+    void assertPointerDisplayIdNotified(ui::LogicalDisplayId displayId) {
         ASSERT_EQ(displayId, mPointerDisplayIdNotified);
         mPointerDisplayIdNotified.reset();
     }
@@ -129,7 +134,7 @@
 private:
     std::deque<std::pair<ControllerType, std::shared_ptr<FakePointerController>>>
             mCreatedControllers;
-    std::optional<int32_t> mPointerDisplayIdNotified;
+    std::optional<ui::LogicalDisplayId> mPointerDisplayIdNotified;
 
     std::shared_ptr<PointerControllerInterface> createPointerController(
             ControllerType type) override {
@@ -139,21 +144,25 @@
         return pc;
     }
 
-    void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override {
+    void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
+                                       const FloatPoint& position) override {
         mPointerDisplayIdNotified = displayId;
     }
 };
 
 TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) {
-    const std::vector<NotifyArgs> allArgs{NotifyInputDevicesChangedArgs{},
-                                          NotifyConfigurationChangedArgs{},
-                                          NotifyKeyArgs{},
-                                          NotifyMotionArgs{},
-                                          NotifySensorArgs{},
-                                          NotifySwitchArgs{},
-                                          NotifyDeviceResetArgs{},
-                                          NotifyPointerCaptureChangedArgs{},
-                                          NotifyVibratorStateArgs{}};
+    const std::vector<NotifyArgs>
+            allArgs{NotifyInputDevicesChangedArgs{},
+                    NotifyConfigurationChangedArgs{},
+                    KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build(),
+                    MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                            .pointer(FIRST_TOUCH_POINTER)
+                            .build(),
+                    NotifySensorArgs{},
+                    NotifySwitchArgs{},
+                    NotifyDeviceResetArgs{},
+                    NotifyPointerCaptureChangedArgs{},
+                    NotifyVibratorStateArgs{}};
 
     for (auto notifyArgs : allArgs) {
         mChoreographer.notify(notifyArgs);
@@ -193,13 +202,17 @@
 
 TEST_F(PointerChoreographerTest, WhenMouseIsAddedCreatesPointerController) {
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
 }
 
 TEST_F(PointerChoreographerTest, WhenMouseIsRemovedRemovesPointerController) {
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
 
     // Remove the mouse.
@@ -210,7 +223,8 @@
 TEST_F(PointerChoreographerTest, WhenKeyboardIsAddedDoesNotCreatePointerController) {
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE)}});
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerNotCreated();
 }
 
@@ -244,7 +258,9 @@
     // For a mouse event without a target display, default viewport should be set for
     // the PointerController.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertViewportSet(DISPLAY_ID);
     ASSERT_TRUE(pc->isPointerShown());
@@ -256,7 +272,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
     firstDisplayPc->assertViewportSet(DISPLAY_ID);
     ASSERT_TRUE(firstDisplayPc->isPointerShown());
@@ -275,7 +293,9 @@
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
 
     assertPointerDisplayIdNotified(DISPLAY_ID);
@@ -284,7 +304,9 @@
 TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterCallsNotifyPointerDisplayIdChanged) {
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotNotified();
 
@@ -296,12 +318,14 @@
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotified(DISPLAY_ID);
 
     mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
-    assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+    assertPointerDisplayIdNotified(ui::LogicalDisplayId::INVALID);
     assertPointerControllerRemoved(pc);
 }
 
@@ -312,7 +336,9 @@
     // Set one viewport as a default mouse display ID.
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotified(DISPLAY_ID);
 
@@ -328,7 +354,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -340,7 +368,7 @@
             MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
                     .pointer(MOUSE_POINTER)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
 
     // Check that the PointerController updated the position and the pointer is shown.
@@ -352,6 +380,40 @@
             AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
 }
 
+TEST_F(PointerChoreographerTest, AbsoluteMouseMovesPointerAndReturnsNewArgs) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(100, 200);
+    const auto absoluteMousePointer = PointerBuilder(/*id=*/0, ToolType::MOUSE)
+                                              .axis(AMOTION_EVENT_AXIS_X, 110)
+                                              .axis(AMOTION_EVENT_AXIS_Y, 220);
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(absoluteMousePointer)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ui::LogicalDisplayId::INVALID)
+                    .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    pc->assertPosition(110, 220);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Check that x-y coordinates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(110, 220), WithRelativeMotion(10, 20), WithDisplayId(DISPLAY_ID),
+                  WithCursorPosition(110, 220)));
+}
+
 TEST_F(PointerChoreographerTest,
        AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
     // Add two displays and set one to default.
@@ -361,7 +423,7 @@
     // Add two devices, one unassociated and the other associated with non-default mouse display.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
               generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
     auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
@@ -397,7 +459,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -407,10 +471,12 @@
     // Assume that pointer capture is enabled.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/1,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}});
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE,
+                                     ui::LogicalDisplayId::INVALID)}});
     mChoreographer.notifyPointerCaptureChanged(
             NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC),
-                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+                                            PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+                                                                  /*seq=*/0)));
 
     // Notify motion as if pointer capture is enabled.
     mChoreographer.notifyMotion(
@@ -421,7 +487,7 @@
                                      .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
                                      .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
 
     // Check that there's no update on the PointerController.
@@ -430,7 +496,8 @@
 
     // Check x-y coordinates, displayId and cursor position are not changed.
     mTestListener.assertNotifyMotionWasCalled(
-            AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), WithDisplayId(ADISPLAY_ID_NONE),
+            AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20),
+                  WithDisplayId(ui::LogicalDisplayId::INVALID),
                   WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                      AMOTION_EVENT_INVALID_CURSOR_POSITION)));
 }
@@ -439,7 +506,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
     ASSERT_TRUE(pc->isPointerShown());
@@ -447,7 +516,8 @@
     // Enable pointer capture and check if the PointerController hid the pointer.
     mChoreographer.notifyPointerCaptureChanged(
             NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
-                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+                                            PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+                                                                  /*seq=*/0)));
     ASSERT_FALSE(pc->isPointerShown());
 }
 
@@ -457,7 +527,9 @@
 
     // A mouse is connected, and the pointer is shown.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
 
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_TRUE(pc->isPointerShown());
@@ -467,15 +539,17 @@
     // Add a second mouse is added, the pointer is shown again.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
-              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     ASSERT_TRUE(pc->isPointerShown());
 
     // One of the mice is removed, and it does not cause the mouse pointer to fade, because
     // we have one more mouse connected.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+             {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerNotRemoved(pc);
     ASSERT_TRUE(pc->isPointerShown());
 
@@ -488,7 +562,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
 
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_TRUE(pc->isPointerShown());
@@ -498,8 +574,9 @@
     // Adding a touchscreen device does not unfade the mouse pointer.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
-              generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID,
+                                     AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
                                      DISPLAY_ID)}});
 
     ASSERT_FALSE(pc->isPointerShown());
@@ -510,6 +587,75 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
+TEST_F(PointerChoreographerTest, DisabledMouseConnected) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo mouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID);
+    // Disable this mouse device.
+    mouseDeviceInfo.setEnabled(false);
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    // Disabled mouse device should not create PointerController
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, MouseDeviceDisableLater) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo mouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    auto pc = assertPointerControllerCreated(PointerControllerInterface::ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Now we disable this mouse device
+    mouseDeviceInfo.setEnabled(false);
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    // Because the mouse device disabled, the PointerController should be removed.
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MultipleEnabledAndDisabledMiceConnectionAndRemoval) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo disabledMouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID);
+    disabledMouseDeviceInfo.setEnabled(false);
+
+    InputDeviceInfo enabledMouseDeviceInfo =
+            generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                   ui::LogicalDisplayId::INVALID);
+
+    InputDeviceInfo anotherEnabledMouseDeviceInfo =
+            generateTestDeviceInfo(THIRD_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                   ui::LogicalDisplayId::INVALID);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {disabledMouseDeviceInfo, enabledMouseDeviceInfo, anotherEnabledMouseDeviceInfo}});
+
+    // Mouse should show, because we have two enabled mice device.
+    auto pc = assertPointerControllerCreated(PointerControllerInterface::ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Now we remove one of enabled mice device.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {disabledMouseDeviceInfo, enabledMouseDeviceInfo}});
+
+    // Because we still have an enabled mouse device, pointer should still show.
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // We finally remove last enabled mouse device.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {disabledMouseDeviceInfo}});
+
+    // The PointerController should be removed, because there is no enabled mouse device.
+    assertPointerControllerRemoved(pc);
+}
+
 TEST_F(PointerChoreographerTest, WhenShowTouchesEnabledAndDisabledDoesNotCreatePointerController) {
     // Disable show touches and add a touch device.
     mChoreographer.setShowTouchesEnabled(false);
@@ -722,12 +868,28 @@
     assertPointerControllerRemoved(pc);
 }
 
-TEST_F(PointerChoreographerTest,
-       WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+using StylusFixtureParam =
+        std::tuple</*name*/ std::string_view, /*source*/ uint32_t, ControllerType>;
+
+class StylusTestFixture : public PointerChoreographerTest,
+                          public ::testing::WithParamInterface<StylusFixtureParam> {};
+
+INSTANTIATE_TEST_SUITE_P(PointerChoreographerTest, StylusTestFixture,
+                         ::testing::Values(std::make_tuple("DirectStylus", AINPUT_SOURCE_STYLUS,
+                                                           ControllerType::STYLUS),
+                                           std::make_tuple("DrawingTablet", DRAWING_TABLET_SOURCE,
+                                                           ControllerType::MOUSE)),
+                         [](const testing::TestParamInfo<StylusFixtureParam>& p) {
+                             return std::string{std::get<0>(p.param)};
+                         });
+
+TEST_P(StylusTestFixture, WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Disable stylus pointer icon and add a stylus device.
     mChoreographer.setStylusPointerIconEnabled(false);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     assertPointerControllerNotCreated();
 
     // Enable stylus pointer icon. PointerController still should not be created.
@@ -735,25 +897,25 @@
     assertPointerControllerNotCreated();
 }
 
-TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) {
+TEST_P(StylusTestFixture, WhenStylusHoverEventOccursCreatesPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Add a stylus device and enable stylus pointer icon.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
     assertPointerControllerNotCreated();
 
     // Emit hover event. Now PointerController should be created.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    assertPointerControllerCreated(controllerType);
 }
 
-TEST_F(PointerChoreographerTest,
-       WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) {
+TEST_F(PointerChoreographerTest, StylusHoverEventWhenStylusPointerIconDisabled) {
     // Add a stylus device and disable stylus pointer icon.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
@@ -770,25 +932,43 @@
     assertPointerControllerNotCreated();
 }
 
-TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) {
-    // Make sure the PointerController is created.
+TEST_F(PointerChoreographerTest, DrawingTabletHoverEventWhenStylusPointerIconDisabled) {
+    // Add a drawing tablet and disable stylus pointer icon.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
-    mChoreographer.setStylusPointerIconEnabled(true);
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(false);
+    assertPointerControllerNotCreated();
+
+    // Emit hover event. Drawing tablets are not affected by "stylus pointer icon" setting.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
                     .pointer(STYLUS_POINTER)
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
                     .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    assertPointerControllerCreated(ControllerType::MOUSE);
+}
+
+TEST_P(StylusTestFixture, WhenStylusDeviceIsRemovedRemovesPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Remove the device.
     mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
     assertPointerControllerRemoved(pc);
 }
 
-TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) {
+TEST_F(PointerChoreographerTest, StylusPointerIconDisabledRemovesPointerController) {
     // Make sure the PointerController is created.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
@@ -806,38 +986,59 @@
     assertPointerControllerRemoved(pc);
 }
 
-TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) {
+TEST_F(PointerChoreographerTest,
+       StylusPointerIconDisabledDoesNotRemoveDrawingTabletPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+    // Disable stylus pointer icon. This should not affect drawing tablets.
+    mChoreographer.setStylusPointerIconEnabled(false);
+    assertPointerControllerNotRemoved(pc);
+}
+
+TEST_P(StylusTestFixture, SetsViewportForStylusPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Set viewport.
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
     // Make sure the PointerController is created.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Check that viewport is set for the PointerController.
     pc->assertViewportSet(DISPLAY_ID);
 }
 
-TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+TEST_P(StylusTestFixture, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure the PointerController is created.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Check that viewport is unset.
     pc->assertViewportNotSet();
@@ -849,19 +1050,19 @@
     pc->assertViewportSet(DISPLAY_ID);
 }
 
-TEST_F(PointerChoreographerTest,
-       WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+TEST_P(StylusTestFixture, WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure the PointerController is created.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Check that viewport is unset.
     pc->assertViewportNotSet();
@@ -873,24 +1074,25 @@
     pc->assertViewportNotSet();
 }
 
-TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) {
+TEST_P(StylusTestFixture, StylusHoverManipulatesPointer) {
+    const auto& [name, source, controllerType] = GetParam();
+
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
     // Emit hover enter event. This is for creating PointerController.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Emit hover move event. After bounds are set, PointerController will update the position.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
@@ -900,7 +1102,7 @@
 
     // Emit hover exit event.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, source)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
@@ -909,38 +1111,38 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
-TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) {
+TEST_P(StylusTestFixture, StylusHoverManipulatesPointerForTwoDisplays) {
+    const auto& [name, source, controllerType] = GetParam();
+
     mChoreographer.setStylusPointerIconEnabled(true);
     // Add two stylus devices associated to different displays.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
-              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}});
+             {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, source, ANOTHER_DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
 
     // Emit hover event with first device. This is for creating PointerController.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto firstDisplayPc = assertPointerControllerCreated(controllerType);
 
     // Emit hover event with second device. This is for creating PointerController.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(SECOND_DEVICE_ID)
-                    .displayId(ANOTHER_DISPLAY_ID)
-                    .build());
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .displayId(ANOTHER_DISPLAY_ID)
+                                        .build());
 
     // There should be another PointerController created.
-    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    auto secondDisplayPc = assertPointerControllerCreated(controllerType);
 
     // Emit hover event with first device.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
@@ -952,7 +1154,7 @@
 
     // Emit hover event with second device.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350))
                     .deviceId(SECOND_DEVICE_ID)
                     .displayId(ANOTHER_DISPLAY_ID)
@@ -967,19 +1169,20 @@
     ASSERT_TRUE(firstDisplayPc->isPointerShown());
 }
 
-TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetRemovesPointer) {
+TEST_P(StylusTestFixture, WhenStylusDeviceIsResetRemovesPointer) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure the PointerController is created and there is a pointer.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
     ASSERT_TRUE(pc->isPointerShown());
 
     // Reset the device and see the pointer controller was removed.
@@ -991,7 +1194,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
 }
 
@@ -999,7 +1202,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
 
     // Remove the touchpad.
@@ -1040,7 +1243,9 @@
     // For a touchpad event without a target display, default viewport should be set for
     // the PointerController.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertViewportSet(DISPLAY_ID);
 }
@@ -1053,7 +1258,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
     firstDisplayPc->assertViewportSet(DISPLAY_ID);
 
@@ -1071,7 +1276,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
 
     assertPointerDisplayIdNotified(DISPLAY_ID);
@@ -1082,7 +1287,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotNotified();
 
@@ -1096,12 +1301,12 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotified(DISPLAY_ID);
 
     mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
-    assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+    assertPointerDisplayIdNotified(ui::LogicalDisplayId::INVALID);
     assertPointerControllerRemoved(pc);
 }
 
@@ -1115,12 +1320,12 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotified(DISPLAY_ID);
 
-    // Set another viewport as a default mouse display ID. ADISPLAY_ID_NONE will be notified
-    // before a touchpad event.
+    // Set another viewport as a default mouse display ID. ui::LogicalDisplayId::INVALID will be
+    // notified before a touchpad event.
     mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
     assertPointerControllerRemoved(firstDisplayPc);
 
@@ -1134,7 +1339,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -1146,7 +1351,7 @@
             MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
                     .pointer(TOUCHPAD_POINTER)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
 
     // Check that the PointerController updated the position and the pointer is shown.
@@ -1164,7 +1369,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -1178,7 +1383,7 @@
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
                     .classification(MotionClassification::MULTI_FINGER_SWIPE)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
@@ -1192,7 +1397,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0))
                     .classification(MotionClassification::MULTI_FINGER_SWIPE)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
@@ -1209,7 +1414,7 @@
                     .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(100).y(0))
                     .classification(MotionClassification::MULTI_FINGER_SWIPE)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
@@ -1224,7 +1429,7 @@
                     .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(110).y(10))
                     .classification(MotionClassification::MULTI_FINGER_SWIPE)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
@@ -1243,7 +1448,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE),
+                                     ui::LogicalDisplayId::INVALID),
               generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
                                      ANOTHER_DISPLAY_ID)}});
     auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
@@ -1282,7 +1487,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -1292,13 +1497,14 @@
     // Assume that pointer capture is enabled.
     mChoreographer.notifyPointerCaptureChanged(
             NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
-                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+                                            PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+                                                                  /*seq=*/0)));
 
     // Notify motion as if pointer capture is enabled.
     mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD)
                                         .pointer(FIRST_TOUCH_POINTER)
                                         .deviceId(DEVICE_ID)
-                                        .displayId(ADISPLAY_ID_NONE)
+                                        .displayId(ui::LogicalDisplayId::INVALID)
                                         .build());
 
     // Check that there's no update on the PointerController.
@@ -1307,7 +1513,7 @@
 
     // Check x-y coordinates, displayId and cursor position are not changed.
     mTestListener.assertNotifyMotionWasCalled(
-            AllOf(WithCoords(100, 200), WithDisplayId(ADISPLAY_ID_NONE),
+            AllOf(WithCoords(100, 200), WithDisplayId(ui::LogicalDisplayId::INVALID),
                   WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                      AMOTION_EVENT_INVALID_CURSOR_POSITION)));
 }
@@ -1318,7 +1524,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
     ASSERT_TRUE(pc->isPointerShown());
@@ -1326,7 +1532,8 @@
     // Enable pointer capture and check if the PointerController hid the pointer.
     mChoreographer.notifyPointerCaptureChanged(
             NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
-                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+                                            PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+                                                                  /*seq=*/0)));
     ASSERT_FALSE(pc->isPointerShown());
 }
 
@@ -1335,7 +1542,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertPointerIconNotSet();
 
@@ -1349,7 +1558,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertPointerIconNotSet();
 
@@ -1364,7 +1575,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertPointerIconNotSet();
 
@@ -1379,7 +1592,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertCustomPointerIconNotSet();
 
@@ -1402,7 +1617,7 @@
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
               generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
     auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
@@ -1421,19 +1636,90 @@
     firstMousePc->assertPointerIconNotSet();
 }
 
-TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) {
-    // Make sure there is a PointerController.
+TEST_F_WITH_FLAGS(PointerChoreographerTest, HidesTouchSpotsOnMirroredDisplaysForSecureWindow,
+                  REQUIRES_FLAGS_ENABLED(
+                          ACONFIG_FLAG(input_flags, hide_pointer_indicators_for_secure_windows))) {
+    // Add a touch device and enable show touches.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
-    mChoreographer.setStylusPointerIconEnabled(true);
-    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+
+    // Emit touch events to create PointerController
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
                     .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // By default touch indicators should not be hidden
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+
+    // adding secure window on display should set flag to hide pointer indicators on corresponding
+    // mirrored display
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    mChoreographer.onWindowInfosChanged({windowInfo});
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/true);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+
+    // removing the secure window should reset the state
+    windowInfo.inputConfig.clear(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY);
+    mChoreographer.onWindowInfosChanged({windowInfo});
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+}
+
+TEST_F_WITH_FLAGS(PointerChoreographerTest,
+                  DoesNotHidesTouchSpotsOnMirroredDisplaysForInvisibleWindow,
+                  REQUIRES_FLAGS_ENABLED(
+                          ACONFIG_FLAG(input_flags, hide_pointer_indicators_for_secure_windows))) {
+    // Add a touch device and enable show touches.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+
+    // Emit touch events to create PointerController
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // By default touch indicators should not be hidden
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+
+    // adding secure but hidden window on display should still not set flag to hide pointer
+    // indicators
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_VISIBLE;
+    mChoreographer.onWindowInfosChanged({windowInfo});
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+}
+
+TEST_P(StylusTestFixture, SetsPointerIconForStylus) {
+    const auto& [name, source, controllerType] = GetParam();
+
+    // Make sure there is a PointerController.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
     pc->assertPointerIconNotSet();
 
     // Set pointer icon for the device.
@@ -1444,21 +1730,30 @@
     ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
                                                SECOND_DEVICE_ID));
     pc->assertPointerIconNotSet();
+
+    // The stylus stops hovering. This should cause the icon to be reset.
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_NOT_SPECIFIED);
 }
 
-TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) {
+TEST_P(StylusTestFixture, SetsCustomPointerIconForStylus) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure there is a PointerController.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
     pc->assertCustomPointerIconNotSet();
 
     // Set custom pointer icon for the device.
@@ -1474,28 +1769,28 @@
     pc->assertCustomPointerIconNotSet();
 }
 
-TEST_F(PointerChoreographerTest, SetsPointerIconForTwoStyluses) {
+TEST_P(StylusTestFixture, SetsPointerIconForTwoStyluses) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure there are two StylusPointerControllers. They can be on a same display.
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
-              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+             {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto firstStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(SECOND_DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto secondStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto firstStylusPc = assertPointerControllerCreated(controllerType);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto secondStylusPc = assertPointerControllerCreated(controllerType);
 
     // Set pointer icon for one stylus.
     ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
@@ -1509,29 +1804,30 @@
     firstStylusPc->assertPointerIconNotSet();
 }
 
-TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) {
+TEST_P(StylusTestFixture, SetsPointerIconForMouseAndStylus) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure there are PointerControllers for a mouse and a stylus.
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
-              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.notifyMotion(
             MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
                     .pointer(MOUSE_POINTER)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(SECOND_DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto stylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto stylusPc = assertPointerControllerCreated(controllerType);
 
     // Set pointer icon for the mouse.
     ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
@@ -1545,4 +1841,236 @@
     mousePc->assertPointerIconNotSet();
 }
 
+TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerOnDisplay) {
+    // Make sure there are two PointerControllers on different displays.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+    auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
+    auto secondMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, secondMousePc->getDisplayId());
+
+    // Both pointers should be visible.
+    ASSERT_TRUE(firstMousePc->isPointerShown());
+    ASSERT_TRUE(secondMousePc->isPointerShown());
+
+    // Hide the icon on the second display.
+    mChoreographer.setPointerIconVisibility(ANOTHER_DISPLAY_ID, false);
+    ASSERT_TRUE(firstMousePc->isPointerShown());
+    ASSERT_FALSE(secondMousePc->isPointerShown());
+
+    // Move and set pointer icons for both mice. The second pointer should still be hidden.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+                                              SECOND_DEVICE_ID));
+    firstMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    secondMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    ASSERT_TRUE(firstMousePc->isPointerShown());
+    ASSERT_FALSE(secondMousePc->isPointerShown());
+
+    // Allow the icon to be visible on the second display, and move the mouse.
+    mChoreographer.setPointerIconVisibility(ANOTHER_DISPLAY_ID, true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    ASSERT_TRUE(firstMousePc->isPointerShown());
+    ASSERT_TRUE(secondMousePc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerWhenDeviceConnected) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Hide the pointer on the display, and then connect the mouse.
+    mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, mousePc->getDisplayId());
+
+    // The pointer should not be visible.
+    ASSERT_FALSE(mousePc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForTouchpad) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Hide the pointer on the display.
+    mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ui::LogicalDisplayId::INVALID)}});
+    auto touchpadPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, touchpadPc->getDisplayId());
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                  AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD)
+                                        .pointer(TOUCHPAD_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+
+    // The pointer should not be visible.
+    ASSERT_FALSE(touchpadPc->isPointerShown());
+}
+
+TEST_P(StylusTestFixture, SetPointerIconVisibilityHidesPointerForStylus) {
+    const auto& [name, source, controllerType] = GetParam();
+
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setStylusPointerIconEnabled(true);
+
+    // Hide the pointer on the display.
+    mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    auto pc = assertPointerControllerCreated(controllerType);
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+
+    // The pointer should not be visible.
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, DrawingTabletCanReportMouseEvent) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    // There should be no controller created when a drawing tablet is connected
+    assertPointerControllerNotCreated();
+
+    // But if it ends up reporting a mouse event, then the mouse controller will be created
+    // dynamically.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // The controller is removed when the drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // First drawing tablet is added
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    assertPointerControllerNotCreated();
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Second drawing tablet is added
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    assertPointerControllerNotRemoved(pc);
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // First drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    assertPointerControllerNotRemoved(pc);
+
+    // Second drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Mouse and drawing tablet connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Drawing tablet reports a mouse event
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // Remove the mouse device
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+
+    // The mouse controller should not be removed, because the drawing tablet has produced a
+    // mouse event, so we are treating it as a mouse too.
+    assertPointerControllerNotRemoved(pc);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index 9818176..a36d526 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include "../PreferStylusOverTouchBlocker.h"
 
 namespace android {
@@ -64,8 +63,9 @@
     }
 
     // Define a valid motion event.
-    NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, /*displayId=*/0,
-                          POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0,
+    NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source,
+                          ui::LogicalDisplayId::DEFAULT, POLICY_FLAG_PASS_TO_USER, action,
+                          /* actionButton */ 0,
                           /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
                           pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
@@ -439,7 +439,7 @@
     InputDeviceInfo stylusDevice;
     stylusDevice.initialize(STYLUS_DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1,
                             /*identifier=*/{}, "stylus device", /*external=*/false,
-                            /*hasMic=*/false, ADISPLAY_ID_NONE);
+                            /*hasMic=*/false, ui::LogicalDisplayId::INVALID);
     notifyInputDevicesChanged({stylusDevice});
     // The touchscreen device was removed, so we no longer remember anything about it. We should
     // again start blocking touch events from it.
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index 66fdaa4..65fb9c6 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -18,6 +18,7 @@
 
 #include <cmath>
 #include <compare>
+#include <ios>
 
 #include <android-base/stringprintf.h>
 #include <android/input.h>
@@ -144,7 +145,7 @@
 class WithDisplayIdMatcher {
 public:
     using is_gtest_matcher = void;
-    explicit WithDisplayIdMatcher(int32_t displayId) : mDisplayId(displayId) {}
+    explicit WithDisplayIdMatcher(ui::LogicalDisplayId displayId) : mDisplayId(displayId) {}
 
     bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
         return mDisplayId == args.displayId;
@@ -163,10 +164,10 @@
     void DescribeNegationTo(std::ostream* os) const { *os << "wrong display id"; }
 
 private:
-    const int32_t mDisplayId;
+    const ui::LogicalDisplayId mDisplayId;
 };
 
-inline WithDisplayIdMatcher WithDisplayId(int32_t displayId) {
+inline WithDisplayIdMatcher WithDisplayId(ui::LogicalDisplayId displayId) {
     return WithDisplayIdMatcher(displayId);
 }
 
@@ -464,6 +465,53 @@
     return WithPointersMatcher(pointers);
 }
 
+/// Pointer ids matcher
+class WithPointerIdsMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithPointerIdsMatcher(std::set<int32_t> pointerIds) : mPointerIds(pointerIds) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+        std::set<int32_t> actualPointerIds;
+        for (size_t pointerIndex = 0; pointerIndex < event.getPointerCount(); pointerIndex++) {
+            const PointerProperties* properties = event.getPointerProperties(pointerIndex);
+            actualPointerIds.insert(properties->id);
+        }
+
+        if (mPointerIds != actualPointerIds) {
+            *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got "
+                << dumpSet(actualPointerIds);
+            return false;
+        }
+        return true;
+    }
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        std::set<int32_t> actualPointerIds;
+        for (const PointerProperties& properties : event.pointerProperties) {
+            actualPointerIds.insert(properties.id);
+        }
+
+        if (mPointerIds != actualPointerIds) {
+            *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got "
+                << dumpSet(actualPointerIds);
+            return false;
+        }
+        return true;
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with pointer ids " << dumpSet(mPointerIds); }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointer ids"; }
+
+private:
+    const std::set<int32_t> mPointerIds;
+};
+
+inline WithPointerIdsMatcher WithPointerIds(const std::set<int32_t /*id*/>& pointerIds) {
+    return WithPointerIdsMatcher(pointerIds);
+}
+
 /// Key code
 class WithKeyCodeMatcher {
 public:
@@ -631,6 +679,24 @@
     return argPressure == pressure;
 }
 
+MATCHER_P(WithSize, size, "MotionEvent with specified size") {
+    const auto argSize = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SIZE);
+    *result_listener << "expected size " << size << ", but got " << argSize;
+    return argSize == size;
+}
+
+MATCHER_P(WithOrientation, orientation, "MotionEvent with specified orientation") {
+    const auto argOrientation = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+    *result_listener << "expected orientation " << orientation << ", but got " << argOrientation;
+    return argOrientation == orientation;
+}
+
+MATCHER_P(WithDistance, distance, "MotionEvent with specified distance") {
+    const auto argDistance = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_DISTANCE);
+    *result_listener << "expected distance " << distance << ", but got " << argDistance;
+    return argDistance == distance;
+}
+
 MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
     const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
     const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
@@ -674,6 +740,12 @@
     return arg.buttonState == buttons;
 }
 
+MATCHER_P(WithMetaState, metaState, "InputEvent with specified meta state") {
+    *result_listener << "expected meta state 0x" << std::hex << metaState << ", but got 0x"
+                     << arg.metaState;
+    return arg.metaState == metaState;
+}
+
 MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") {
     *result_listener << "expected action button " << actionButton << ", but got "
                      << arg.actionButton;
@@ -696,4 +768,16 @@
     return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision;
 }
 
+MATCHER_P(WithPolicyFlags, policyFlags, "InputEvent with specified policy flags") {
+    *result_listener << "expected policy flags 0x" << std::hex << policyFlags << ", but got 0x"
+                     << arg.policyFlags;
+    return arg.policyFlags == static_cast<uint32_t>(policyFlags);
+}
+
+MATCHER_P(WithEdgeFlags, edgeFlags, "InputEvent with specified edge flags") {
+    *result_listener << "expected edge flags 0x" << std::hex << edgeFlags << ", but got 0x"
+                     << arg.edgeFlags;
+    return arg.edgeFlags == edgeFlags;
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 8cf738c..2b62dd1 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -19,9 +19,7 @@
 #include <android-base/logging.h>
 #include <gtest/gtest.h>
 
-#include <com_android_input_flags.h>
 #include <thread>
-#include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InterfaceMocks.h"
 #include "TestEventMatchers.h"
@@ -37,17 +35,17 @@
 constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
-constexpr int32_t DISPLAY_ID = 0;
+constexpr auto HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER;
+constexpr auto HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
 constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
 
-namespace input_flags = com::android::input::flags;
-
 /**
  * Unit tests for TouchpadInputMapper.
  */
-class TouchpadInputMapperTestBase : public InputMapperUnitTest {
+class TouchpadInputMapperTest : public InputMapperUnitTest {
 protected:
     void SetUp() override {
         InputMapperUnitTest::SetUp();
@@ -101,24 +99,24 @@
         setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
         setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
         setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TRACKING_ID, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
         EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_))
                 .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) {
                     *outValue = 0;
                     return OK;
                 });
+        EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, testing::_, testing::_))
+                .WillRepeatedly([]() -> base::Result<std::vector<int32_t>> {
+                    return base::ResultError("Axis not supported", NAME_NOT_FOUND);
+                });
+        createDevice();
         mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 };
 
-class TouchpadInputMapperTest : public TouchpadInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(false);
-        TouchpadInputMapperTestBase::SetUp();
-    }
-};
-
 /**
  * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
  * generated when hovering stops. Currently, it is not.
@@ -126,65 +124,6 @@
  * but only after the button is released.
  */
 TEST_F(TouchpadInputMapperTest, HoverAndLeftButtonPress) {
-    std::list<NotifyArgs> args;
-
-    args += process(EV_ABS, ABS_MT_TRACKING_ID, 1);
-    args += process(EV_KEY, BTN_TOUCH, 1);
-    setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER});
-    args += process(EV_KEY, BTN_TOOL_FINGER, 1);
-    args += process(EV_ABS, ABS_MT_POSITION_X, 50);
-    args += process(EV_ABS, ABS_MT_POSITION_Y, 50);
-    args += process(EV_ABS, ABS_MT_PRESSURE, 1);
-    args += process(EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args, testing::IsEmpty());
-
-    // Without this sleep, the test fails.
-    // TODO(b/284133337): Figure out whether this can be removed
-    std::this_thread::sleep_for(std::chrono::milliseconds(20));
-
-    args += process(EV_KEY, BTN_LEFT, 1);
-    setScanCodeState(KeyState::DOWN, {BTN_LEFT});
-    args += process(EV_SYN, SYN_REPORT, 0);
-
-    args += process(EV_KEY, BTN_LEFT, 0);
-    setScanCodeState(KeyState::UP, {BTN_LEFT});
-    args += process(EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
-
-    // Liftoff
-    args.clear();
-    args += process(EV_ABS, ABS_MT_PRESSURE, 0);
-    args += process(EV_ABS, ABS_MT_TRACKING_ID, -1);
-    args += process(EV_KEY, BTN_TOUCH, 0);
-    setScanCodeState(KeyState::UP, {BTN_TOOL_FINGER});
-    args += process(EV_KEY, BTN_TOOL_FINGER, 0);
-    args += process(EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args, testing::IsEmpty());
-}
-
-class TouchpadInputMapperTestWithChoreographer : public TouchpadInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(true);
-        TouchpadInputMapperTestBase::SetUp();
-    }
-};
-
-// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
-//   logic can be removed.
-/**
- * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
- * generated when hovering stops. Currently, it is not.
- * In the current implementation, HOVER_MOVE and ACTION_DOWN events are not sent out right away,
- * but only after the button is released.
- */
-TEST_F(TouchpadInputMapperTestWithChoreographer, HoverAndLeftButtonPress) {
     mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                     /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
@@ -217,12 +156,14 @@
     setScanCodeState(KeyState::UP, {BTN_LEFT});
     args += process(EV_SYN, SYN_REPORT, 0);
     ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_EXIT)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER))));
 
     // Liftoff
     args.clear();
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index 78f7291..853f628 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -18,7 +18,6 @@
 #include <android-base/silent_death_test.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <linux/input.h>
 #include <thread>
 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
@@ -89,7 +88,8 @@
 
     // Define a valid motion event.
     NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
-                          /*displayId=*/0, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0,
+                          ui::LogicalDisplayId::DEFAULT, POLICY_FLAG_PASS_TO_USER, action,
+                          /*actionButton=*/0,
                           /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
                           pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
@@ -104,7 +104,7 @@
 
     auto info = InputDeviceInfo();
     info.initialize(DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, identifier, "alias",
-                    /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+                    /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID);
     info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
     info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat=*/0,
                         /*fuzz=*/0, X_RESOLUTION);
@@ -434,7 +434,7 @@
 TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) {
     // Create a basic key event and send to blocker
     NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3,
-                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0,
+                       AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/0,
                        AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
                        AMETA_NONE, /*downTime=*/6);
 
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 81c3353..48e1954 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -178,7 +178,12 @@
     shared_libs: [
         "libinputreporter",
     ],
+    static_libs: [
+        "libgmock",
+        "libgtest",
+    ],
     srcs: [
+        ":inputdispatcher_common_test_sources",
         "InputDispatcherFuzzer.cpp",
     ],
 }
diff --git a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
index 885820f..812969b 100644
--- a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
+++ b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
@@ -178,7 +178,7 @@
         pointerCoords.push_back(coords);
     }
 
-    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, maxDisplays - 1);
+    const ui::LogicalDisplayId displayId{fdp.ConsumeIntegralInRange<int32_t>(0, maxDisplays - 1)};
     const int32_t deviceId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DEVICES - 1);
 
     // Current time +- 5 seconds
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index deb811d..0b4ac1f 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -54,7 +54,7 @@
                     mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
                                             eventTime, readTime,
                                             /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
-                                            AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT,
+                                            AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT,
                                             /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
                                             AKEY_EVENT_ACTION_DOWN,
                                             /*flags=*/fdp.ConsumeIntegral<int32_t>(), AKEYCODE_HOME,
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
index dc5a213..79a5ff6 100644
--- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -18,7 +18,7 @@
 #include <fuzzer/FuzzedDataProvider.h>
 #include "../FakeApplicationHandle.h"
 #include "../FakeInputDispatcherPolicy.h"
-#include "../FakeWindowHandle.h"
+#include "../FakeWindows.h"
 #include "FuzzedInputStream.h"
 #include "dispatcher/InputDispatcher.h"
 #include "input/InputVerifier.h"
@@ -88,8 +88,9 @@
 
 } // namespace
 
-sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, InputDispatcher& dispatcher,
-                                          int32_t displayId) {
+sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp,
+                                          std::unique_ptr<InputDispatcher>& dispatcher,
+                                          ui::LogicalDisplayId displayId) {
     static size_t windowNumber = 0;
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     std::string windowName = android::base::StringPrintf("Win") + std::to_string(windowNumber++);
@@ -100,10 +101,11 @@
     return window;
 }
 
-void randomizeWindows(
-        std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>>& windowsPerDisplay,
-        FuzzedDataProvider& fdp, InputDispatcher& dispatcher) {
-    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DISPLAYS - 1);
+void randomizeWindows(std::unordered_map<ui::LogicalDisplayId, std::vector<sp<FakeWindowHandle>>>&
+                              windowsPerDisplay,
+                      FuzzedDataProvider& fdp, std::unique_ptr<InputDispatcher>& dispatcher) {
+    const ui::LogicalDisplayId displayId{
+            fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DISPLAYS - 1)};
     std::vector<sp<FakeWindowHandle>>& windows = windowsPerDisplay[displayId];
 
     fdp.PickValueInArray<std::function<void()>>({
@@ -142,12 +144,12 @@
     NotifyStreamProvider streamProvider(fdp);
 
     FakeInputDispatcherPolicy fakePolicy;
-    InputDispatcher dispatcher(fakePolicy);
-    dispatcher.setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+    auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
+    dispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
     // Start InputDispatcher thread
-    dispatcher.start();
+    dispatcher->start();
 
-    std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>> windowsPerDisplay;
+    std::unordered_map<ui::LogicalDisplayId, std::vector<sp<FakeWindowHandle>>> windowsPerDisplay;
 
     // Randomly invoke InputDispatcher api's until randomness is exhausted.
     while (fdp.remaining_bytes() > 0) {
@@ -155,7 +157,7 @@
                 [&]() -> void {
                     std::optional<NotifyMotionArgs> motion = streamProvider.nextMotion();
                     if (motion) {
-                        dispatcher.notifyMotion(*motion);
+                        dispatcher->notifyMotion(*motion);
                     }
                 },
                 [&]() -> void {
@@ -169,7 +171,7 @@
                         }
                     }
 
-                    dispatcher.onWindowInfosChanged(
+                    dispatcher->onWindowInfosChanged(
                             {windowInfos, {}, /*vsyncId=*/0, /*timestamp=*/0});
                 },
                 // Consume on all the windows
@@ -187,7 +189,7 @@
         })();
     }
 
-    dispatcher.stop();
+    dispatcher->stop();
 
     return 0;
 }
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 9223287..7d26a43 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -55,8 +55,6 @@
 
     void monitor() { reader->monitor(); }
 
-    bool isInputDeviceEnabled(int32_t deviceId) { return reader->isInputDeviceEnabled(deviceId); }
-
     status_t start() { return reader->start(); }
 
     status_t stop() { return reader->stop(); }
@@ -119,7 +117,7 @@
         return reader->getSensors(deviceId);
     }
 
-    bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) {
+    bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) {
         return reader->canDispatchToDisplay(deviceId, displayId);
     }
 
@@ -169,6 +167,8 @@
         reader->sysfsNodeChanged(sysfsNodePath);
     }
 
+    DeviceId getLastUsedInputDeviceId() override { return reader->getLastUsedInputDeviceId(); }
+
 private:
     std::unique_ptr<InputReaderInterface> reader;
 };
@@ -204,7 +204,6 @@
                 },
                 [&]() -> void { reader->monitor(); },
                 [&]() -> void { reader->getInputDevices(); },
-                [&]() -> void { reader->isInputDeviceEnabled(fdp->ConsumeIntegral<int32_t>()); },
                 [&]() -> void {
                     reader->getScanCodeState(fdp->ConsumeIntegral<int32_t>(),
                                              fdp->ConsumeIntegral<uint32_t>(),
@@ -241,7 +240,8 @@
                 },
                 [&]() -> void {
                     reader->canDispatchToDisplay(fdp->ConsumeIntegral<int32_t>(),
-                                                 fdp->ConsumeIntegral<int32_t>());
+                                                 ui::LogicalDisplayId{
+                                                         fdp->ConsumeIntegral<int32_t>()});
                 },
                 [&]() -> void {
                     reader->getKeyCodeForKeyLocation(fdp->ConsumeIntegral<int32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 81c570d..25f2f2e 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -201,6 +201,18 @@
                                   int32_t* outValue) const override {
         return mFdp->ConsumeIntegral<status_t>();
     }
+    base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                       size_t slotCount) const override {
+        if (mFdp->ConsumeBool()) {
+            std::vector<int32_t> outValues(slotCount + 1);
+            for (size_t i = 0; i < outValues.size(); i++) {
+                outValues.push_back(mFdp->ConsumeIntegral<int32_t>());
+            }
+            return std::move(outValues);
+        } else {
+            return base::ResultError("Fuzzer", UNKNOWN_ERROR);
+        }
+    }
     bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
                                uint8_t* outFlags) const override {
         return mFdp->ConsumeBool();
@@ -246,56 +258,16 @@
     void sysfsNodeChanged(const std::string& sysfsNodePath) override {}
 };
 
-class FuzzPointerController : public PointerControllerInterface {
-    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
-
-public:
-    FuzzPointerController(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
-    ~FuzzPointerController() {}
-    std::optional<FloatRect> getBounds() const override {
-        if (mFdp->ConsumeBool()) {
-            return {};
-        } else {
-            return FloatRect{mFdp->ConsumeFloatingPoint<float>(),
-                             mFdp->ConsumeFloatingPoint<float>(),
-                             mFdp->ConsumeFloatingPoint<float>(),
-                             mFdp->ConsumeFloatingPoint<float>()};
-        }
-    }
-    void move(float deltaX, float deltaY) override {}
-    void setPosition(float x, float y) override {}
-    FloatPoint getPosition() const override {
-        return {mFdp->ConsumeFloatingPoint<float>(), mFdp->ConsumeFloatingPoint<float>()};
-    }
-    void fade(Transition transition) override {}
-    void unfade(Transition transition) override {}
-    void setPresentation(Presentation presentation) override {}
-    void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                  BitSet32 spotIdBits, int32_t displayId) override {}
-    void clearSpots() override {}
-    int32_t getDisplayId() const override { return mFdp->ConsumeIntegral<int32_t>(); }
-    void setDisplayViewport(const DisplayViewport& displayViewport) override {}
-    void updatePointerIcon(PointerIconStyle iconId) override {}
-    void setCustomPointerIcon(const SpriteIcon& icon) override {}
-    std::string dump() override { return ""; }
-};
-
 class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
     TouchAffineTransformation mTransform;
-    std::shared_ptr<FuzzPointerController> mPointerController;
     std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 protected:
     ~FuzzInputReaderPolicy() {}
 
 public:
-    FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {
-        mPointerController = std::make_shared<FuzzPointerController>(mFdp);
-    }
+    FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override {}
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override {
-        return mPointerController;
-    }
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {}
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
@@ -313,7 +285,7 @@
     void notifyStylusGestureStarted(int32_t, nsecs_t) {}
     bool isInputMethodConnectionActive() override { return mFdp->ConsumeBool(); }
     std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
-            int32_t associatedDisplayId) override {
+            ui::LogicalDisplayId associatedDisplayId) override {
         return {};
     }
 };
@@ -347,10 +319,6 @@
     bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) override {
         return mFdp->ConsumeBool();
     }
-    void fadePointer() override {}
-    std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) override {
-        return mPolicy->obtainPointerController(0);
-    }
     void requestTimeoutAtTime(nsecs_t when) override {}
     int32_t bumpGeneration() override { return mFdp->ConsumeIntegral<int32_t>(); }
     void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) override {}
diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
index be765cc..643e8b9 100644
--- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
@@ -123,7 +123,11 @@
     config.touchpadPointerSpeed = fdp.ConsumeIntegralInRange(-7, 7);
     config.touchpadNaturalScrollingEnabled = fdp.ConsumeBool();
     config.touchpadTapToClickEnabled = fdp.ConsumeBool();
+    config.touchpadTapDraggingEnabled = fdp.ConsumeBool();
     config.touchpadRightClickZoneEnabled = fdp.ConsumeBool();
+
+    config.pointerCaptureRequest.window = fdp.ConsumeBool() ? sp<BBinder>::make() : nullptr;
+    config.pointerCaptureRequest.seq = fdp.ConsumeIntegral<uint32_t>();
 }
 
 } // namespace
@@ -144,7 +148,6 @@
     // Some settings are fuzzed here, as well as in the main loop, to provide randomized data to the
     // TouchpadInputMapper constructor.
     setTouchpadSettings(*fdp, policyConfig);
-    policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
     TouchpadInputMapper& mapper =
             getMapperForDevice<ThreadSafeFuzzedDataProvider, TouchpadInputMapper>(*fdp, device,
                                                                                   policyConfig);
@@ -163,7 +166,6 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     setTouchpadSettings(*fdp, policyConfig);
-                    policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
                     std::list<NotifyArgs> unused =
                             mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
                                                InputReaderConfiguration::Change(
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 1f72e8b..4f65e77 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -17,9 +17,9 @@
         "PowerHalController.cpp",
         "PowerHalLoader.cpp",
         "PowerHalWrapper.cpp",
+        "PowerHintSessionWrapper.cpp",
         "PowerSaveState.cpp",
         "Temperature.cpp",
-        "WorkDuration.cpp",
         "WorkSource.cpp",
         ":libpowermanager_aidl",
     ],
@@ -51,6 +51,10 @@
         "android.hardware.power@1.3",
     ],
 
+    whole_static_libs: [
+        "android.os.hintmanager_aidl-ndk",
+    ],
+
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index 9a23c84..40fd097 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -57,6 +57,10 @@
     PowerHalLoader::unloadAll();
 }
 
+int32_t HalConnector::getAidlVersion() {
+    return PowerHalLoader::getAidlVersion();
+}
+
 // -------------------------------------------------------------------------------------------------
 
 void PowerHalController::init() {
@@ -77,10 +81,26 @@
     return mConnectedHal;
 }
 
+// Using statement expression macro instead of a method lets the static be
+// scoped to the outer method while dodging the need for a support lookup table
+// This only works for AIDL methods that do not vary supported/unsupported depending
+// on their arguments (not setBoost, setMode) which do their own support checks
+#define CACHE_SUPPORT(version, method)                                    \
+    ({                                                                    \
+        static bool support = mHalConnector->getAidlVersion() >= version; \
+        !support ? decltype(method)::unsupported() : ({                   \
+            auto result = method;                                         \
+            if (result.isUnsupported()) {                                 \
+                support = false;                                          \
+            }                                                             \
+            std::move(result);                                            \
+        });                                                               \
+    })
+
 // Check if a call to Power HAL function failed; if so, log the failure and
 // invalidate the current Power HAL handle.
 template <typename T>
-HalResult<T> PowerHalController::processHalResult(HalResult<T> result, const char* fnName) {
+HalResult<T> PowerHalController::processHalResult(HalResult<T>&& result, const char* fnName) {
     if (result.isFailed()) {
         ALOGE("%s failed: %s", fnName, result.errorMessage());
         std::lock_guard<std::mutex> lock(mConnectedHalMutex);
@@ -88,36 +108,64 @@
         mConnectedHal = nullptr;
         mHalConnector->reset();
     }
-    return result;
+    return std::move(result);
 }
 
 HalResult<void> PowerHalController::setBoost(aidl::android::hardware::power::Boost boost,
                                              int32_t durationMs) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    auto result = handle->setBoost(boost, durationMs);
-    return processHalResult(result, "setBoost");
+    return processHalResult(handle->setBoost(boost, durationMs), "setBoost");
 }
 
 HalResult<void> PowerHalController::setMode(aidl::android::hardware::power::Mode mode,
                                             bool enabled) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    auto result = handle->setMode(mode, enabled);
-    return processHalResult(result, "setMode");
+    return processHalResult(handle->setMode(mode, enabled), "setMode");
 }
 
-HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-PowerHalController::createHintSession(int32_t tgid, int32_t uid,
-                                      const std::vector<int32_t>& threadIds,
-                                      int64_t durationNanos) {
+// Aidl-only methods
+
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> PowerHalController::createHintSession(
+        int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    auto result = handle->createHintSession(tgid, uid, threadIds, durationNanos);
-    return processHalResult(result, "createHintSession");
+    return CACHE_SUPPORT(2,
+                         processHalResult(handle->createHintSession(tgid, uid, threadIds,
+                                                                    durationNanos),
+                                          "createHintSession"));
+}
+
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> PowerHalController::createHintSessionWithConfig(
+        int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+        aidl::android::hardware::power::SessionTag tag,
+        aidl::android::hardware::power::SessionConfig* config) {
+    std::shared_ptr<HalWrapper> handle = initHal();
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds,
+                                                                              durationNanos, tag,
+                                                                              config),
+                                          "createHintSessionWithConfig"));
 }
 
 HalResult<int64_t> PowerHalController::getHintSessionPreferredRate() {
     std::shared_ptr<HalWrapper> handle = initHal();
-    auto result = handle->getHintSessionPreferredRate();
-    return processHalResult(result, "getHintSessionPreferredRate");
+    return CACHE_SUPPORT(2,
+                         processHalResult(handle->getHintSessionPreferredRate(),
+                                          "getHintSessionPreferredRate"));
+}
+
+HalResult<aidl::android::hardware::power::ChannelConfig> PowerHalController::getSessionChannel(
+        int tgid, int uid) {
+    std::shared_ptr<HalWrapper> handle = initHal();
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->getSessionChannel(tgid, uid),
+                                          "getSessionChannel"));
+}
+
+HalResult<void> PowerHalController::closeSessionChannel(int tgid, int uid) {
+    std::shared_ptr<HalWrapper> handle = initHal();
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->closeSessionChannel(tgid, uid),
+                                          "closeSessionChannel"));
 }
 
 } // namespace power
diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp
index 2214461..ea284c3 100644
--- a/services/powermanager/PowerHalLoader.cpp
+++ b/services/powermanager/PowerHalLoader.cpp
@@ -60,6 +60,7 @@
 sp<V1_1::IPower> PowerHalLoader::gHalHidlV1_1 = nullptr;
 sp<V1_2::IPower> PowerHalLoader::gHalHidlV1_2 = nullptr;
 sp<V1_3::IPower> PowerHalLoader::gHalHidlV1_3 = nullptr;
+int32_t PowerHalLoader::gAidlInterfaceVersion = 0;
 
 void PowerHalLoader::unloadAll() {
     std::lock_guard<std::mutex> lock(gHalMutex);
@@ -89,6 +90,8 @@
             ndk::SpAIBinder(AServiceManager_waitForService(aidlServiceName.c_str())));
     if (gHalAidl) {
         ALOGI("Successfully connected to Power HAL AIDL service.");
+        gHalAidl->getInterfaceVersion(&gAidlInterfaceVersion);
+
     } else {
         ALOGI("Power HAL AIDL service not available.");
         gHalExists = false;
@@ -128,6 +131,10 @@
     return loadHal<V1_0::IPower>(gHalExists, gHalHidlV1_0, loadFn, "HIDL v1.0");
 }
 
+int32_t PowerHalLoader::getAidlVersion() {
+    return gAidlInterfaceVersion;
+}
+
 // -------------------------------------------------------------------------------------------------
 
 } // namespace power
diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index 76afbfc..bd6685c 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -18,11 +18,10 @@
 #include <aidl/android/hardware/power/Boost.h>
 #include <aidl/android/hardware/power/IPowerHintSession.h>
 #include <aidl/android/hardware/power/Mode.h>
+#include <powermanager/HalResult.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
 
-#include <cinttypes>
-
 using namespace android::hardware::power;
 namespace Aidl = aidl::android::hardware::power;
 
@@ -31,48 +30,60 @@
 namespace power {
 
 // -------------------------------------------------------------------------------------------------
-inline HalResult<void> toHalResult(const ndk::ScopedAStatus& result) {
-    if (result.isOk()) {
-        return HalResult<void>::ok();
-    }
-    ALOGE("Power HAL request failed: %s", result.getDescription().c_str());
-    return HalResult<void>::failed(result.getDescription());
-}
-
-// -------------------------------------------------------------------------------------------------
 
 HalResult<void> EmptyHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) {
-    ALOGV("Skipped setBoost %s with duration %dms because Power HAL not available",
-          toString(boost).c_str(), durationMs);
+    ALOGV("Skipped setBoost %s with duration %dms because %s", toString(boost).c_str(), durationMs,
+          getUnsupportedMessage());
     return HalResult<void>::unsupported();
 }
 
 HalResult<void> EmptyHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
-    ALOGV("Skipped setMode %s to %s because Power HAL not available", toString(mode).c_str(),
-          enabled ? "true" : "false");
+    ALOGV("Skipped setMode %s to %s because %s", toString(mode).c_str(), enabled ? "true" : "false",
+          getUnsupportedMessage());
     return HalResult<void>::unsupported();
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> EmptyHalWrapper::createHintSession(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
-    ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
-          threadIds.size());
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
+    ALOGV("Skipped createHintSession(task num=%zu) because %s", threadIds.size(),
+          getUnsupportedMessage());
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::unsupported();
+}
+
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> EmptyHalWrapper::createHintSessionWithConfig(
+        int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t, Aidl::SessionTag,
+        Aidl::SessionConfig*) {
+    ALOGV("Skipped createHintSessionWithConfig(task num=%zu) because %s", threadIds.size(),
+          getUnsupportedMessage());
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::unsupported();
 }
 
 HalResult<int64_t> EmptyHalWrapper::getHintSessionPreferredRate() {
-    ALOGV("Skipped getHintSessionPreferredRate because Power HAL not available");
+    ALOGV("Skipped getHintSessionPreferredRate because %s", getUnsupportedMessage());
     return HalResult<int64_t>::unsupported();
 }
 
+HalResult<Aidl::ChannelConfig> EmptyHalWrapper::getSessionChannel(int, int) {
+    ALOGV("Skipped getSessionChannel because %s", getUnsupportedMessage());
+    return HalResult<Aidl::ChannelConfig>::unsupported();
+}
+
+HalResult<void> EmptyHalWrapper::closeSessionChannel(int, int) {
+    ALOGV("Skipped closeSessionChannel because %s", getUnsupportedMessage());
+    return HalResult<void>::unsupported();
+}
+
+const char* EmptyHalWrapper::getUnsupportedMessage() {
+    return "Power HAL is not supported";
+}
+
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> HidlHalWrapperV1_0::setBoost(Aidl::Boost boost, int32_t durationMs) {
     if (boost == Aidl::Boost::INTERACTION) {
         return sendPowerHint(V1_3::PowerHint::INTERACTION, durationMs);
     } else {
-        ALOGV("Skipped setBoost %s because Power HAL AIDL not available", toString(boost).c_str());
-        return HalResult<void>::unsupported();
+        return EmptyHalWrapper::setBoost(boost, durationMs);
     }
 }
 
@@ -92,9 +103,7 @@
         case Aidl::Mode::DOUBLE_TAP_TO_WAKE:
             return setFeature(V1_0::Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE, enabled);
         default:
-            ALOGV("Skipped setMode %s because Power HAL AIDL not available",
-                  toString(mode).c_str());
-            return HalResult<void>::unsupported();
+            return EmptyHalWrapper::setMode(mode, enabled);
     }
 }
 
@@ -113,16 +122,8 @@
     return HalResult<void>::fromReturn(ret);
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession(
-        int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
-    ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
-          threadIds.size());
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
-}
-
-HalResult<int64_t> HidlHalWrapperV1_0::getHintSessionPreferredRate() {
-    ALOGV("Skipped getHintSessionPreferredRate because Power HAL not available");
-    return HalResult<int64_t>::unsupported();
+const char* HidlHalWrapperV1_0::getUnsupportedMessage() {
+    return "Power HAL AIDL is not supported";
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -191,7 +192,7 @@
 
     // Quick return if boost is not supported by HAL
     if (idx >= mBoostSupportedArray.size() || mBoostSupportedArray[idx] == HalSupport::OFF) {
-        ALOGV("Skipped setBoost %s because Power HAL doesn't support it", toString(boost).c_str());
+        ALOGV("Skipped setBoost %s because %s", toString(boost).c_str(), getUnsupportedMessage());
         return HalResult<void>::unsupported();
     }
 
@@ -207,14 +208,14 @@
 
         mBoostSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
         if (!isSupported) {
-            ALOGV("Skipped setBoost %s because Power HAL doesn't support it",
-                  toString(boost).c_str());
+            ALOGV("Skipped setBoost %s because %s", toString(boost).c_str(),
+                  getUnsupportedMessage());
             return HalResult<void>::unsupported();
         }
     }
     lock.unlock();
 
-    return toHalResult(mHandle->setBoost(boost, durationMs));
+    return HalResult<void>::fromStatus(mHandle->setBoost(boost, durationMs));
 }
 
 HalResult<void> AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
@@ -223,7 +224,7 @@
 
     // Quick return if mode is not supported by HAL
     if (idx >= mModeSupportedArray.size() || mModeSupportedArray[idx] == HalSupport::OFF) {
-        ALOGV("Skipped setMode %s because Power HAL doesn't support it", toString(mode).c_str());
+        ALOGV("Skipped setMode %s because %s", toString(mode).c_str(), getUnsupportedMessage());
         return HalResult<void>::unsupported();
     }
 
@@ -236,22 +237,31 @@
 
         mModeSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
         if (!isSupported) {
-            ALOGV("Skipped setMode %s because Power HAL doesn't support it",
-                  toString(mode).c_str());
+            ALOGV("Skipped setMode %s because %s", toString(mode).c_str(), getUnsupportedMessage());
             return HalResult<void>::unsupported();
         }
     }
     lock.unlock();
 
-    return toHalResult(mHandle->setMode(mode, enabled));
+    return HalResult<void>::fromStatus(mHandle->setMode(mode, enabled));
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSession(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> AidlHalWrapper::createHintSession(
         int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
     std::shared_ptr<Aidl::IPowerHintSession> appSession;
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
             fromStatus(mHandle->createHintSession(tgid, uid, threadIds, durationNanos, &appSession),
-                       appSession);
+                       std::make_shared<PowerHintSessionWrapper>(std::move(appSession)));
+}
+
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> AidlHalWrapper::createHintSessionWithConfig(
+        int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+        Aidl::SessionTag tag, Aidl::SessionConfig* config) {
+    std::shared_ptr<Aidl::IPowerHintSession> appSession;
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+            fromStatus(mHandle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
+                                                            tag, config, &appSession),
+                       std::make_shared<PowerHintSessionWrapper>(std::move(appSession)));
 }
 
 HalResult<int64_t> AidlHalWrapper::getHintSessionPreferredRate() {
@@ -260,6 +270,20 @@
     return HalResult<int64_t>::fromStatus(result, rate);
 }
 
+HalResult<Aidl::ChannelConfig> AidlHalWrapper::getSessionChannel(int tgid, int uid) {
+    Aidl::ChannelConfig config;
+    auto result = mHandle->getSessionChannel(tgid, uid, &config);
+    return HalResult<Aidl::ChannelConfig>::fromStatus(result, std::move(config));
+}
+
+HalResult<void> AidlHalWrapper::closeSessionChannel(int tgid, int uid) {
+    return HalResult<void>::fromStatus(mHandle->closeSessionChannel(tgid, uid));
+}
+
+const char* AidlHalWrapper::getUnsupportedMessage() {
+    return "Power HAL doesn't support it";
+}
+
 // -------------------------------------------------------------------------------------------------
 
 } // namespace power
diff --git a/services/powermanager/PowerHintSessionWrapper.cpp b/services/powermanager/PowerHintSessionWrapper.cpp
new file mode 100644
index 0000000..930c7fa
--- /dev/null
+++ b/services/powermanager/PowerHintSessionWrapper.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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 <powermanager/PowerHintSessionWrapper.h>
+
+using namespace aidl::android::hardware::power;
+
+namespace android::power {
+
+// Caches support for a given call in a static variable, checking both
+// the return value and interface version.
+#define CACHE_SUPPORT(version, method)                      \
+    ({                                                      \
+        static bool support = mInterfaceVersion >= version; \
+        !support ? decltype(method)::unsupported() : ({     \
+            auto result = method;                           \
+            if (result.isUnsupported()) {                   \
+                support = false;                            \
+            }                                               \
+            std::move(result);                              \
+        });                                                 \
+    })
+
+#define CHECK_SESSION(resultType)                                    \
+    if (mSession == nullptr) {                                       \
+        return HalResult<resultType>::failed("Session not running"); \
+    }
+
+// FWD_CALL just forwards calls from the wrapper to the session object.
+// It only works if the call has no return object, as is the case with all calls
+// except getSessionConfig.
+#define FWD_CALL(version, name, args, untypedArgs)                                              \
+    HalResult<void> PowerHintSessionWrapper::name args {                                        \
+        CHECK_SESSION(void)                                                                     \
+        return CACHE_SUPPORT(version, HalResult<void>::fromStatus(mSession->name untypedArgs)); \
+    }
+
+PowerHintSessionWrapper::PowerHintSessionWrapper(std::shared_ptr<IPowerHintSession>&& session)
+      : mSession(session) {
+    if (mSession != nullptr) {
+        mSession->getInterfaceVersion(&mInterfaceVersion);
+    }
+}
+
+// Support for individual hints/modes is not really handled here since there
+// is no way to check for it, so in the future if a way to check that is added,
+// this will need to be updated.
+
+FWD_CALL(2, updateTargetWorkDuration, (int64_t in_targetDurationNanos), (in_targetDurationNanos));
+FWD_CALL(2, reportActualWorkDuration, (const std::vector<WorkDuration>& in_durations),
+         (in_durations));
+FWD_CALL(2, pause, (), ());
+FWD_CALL(2, resume, (), ());
+FWD_CALL(2, close, (), ());
+FWD_CALL(4, sendHint, (SessionHint in_hint), (in_hint));
+FWD_CALL(4, setThreads, (const std::vector<int32_t>& in_threadIds), (in_threadIds));
+FWD_CALL(5, setMode, (SessionMode in_type, bool in_enabled), (in_type, in_enabled));
+
+HalResult<SessionConfig> PowerHintSessionWrapper::getSessionConfig() {
+    CHECK_SESSION(SessionConfig);
+    SessionConfig config;
+    return CACHE_SUPPORT(5,
+                         HalResult<SessionConfig>::fromStatus(mSession->getSessionConfig(&config),
+                                                              std::move(config)));
+}
+
+} // namespace android::power
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
deleted file mode 100644
index ef723c2..0000000
--- a/services/powermanager/WorkDuration.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "WorkDuration"
-
-#include <android/WorkDuration.h>
-#include <android/performance_hint.h>
-#include <binder/Parcel.h>
-#include <utils/Log.h>
-
-namespace android::os {
-
-WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos,
-                           int64_t cpuDurationNanos, int64_t gpuDurationNanos)
-      : workPeriodStartTimestampNanos(startTimestampNanos),
-        actualTotalDurationNanos(totalDurationNanos),
-        actualCpuDurationNanos(cpuDurationNanos),
-        actualGpuDurationNanos(gpuDurationNanos) {}
-
-status_t WorkDuration::writeToParcel(Parcel* parcel) const {
-    if (parcel == nullptr) {
-        ALOGE("%s: Null parcel", __func__);
-        return BAD_VALUE;
-    }
-
-    parcel->writeInt64(workPeriodStartTimestampNanos);
-    parcel->writeInt64(actualTotalDurationNanos);
-    parcel->writeInt64(actualCpuDurationNanos);
-    parcel->writeInt64(actualGpuDurationNanos);
-    parcel->writeInt64(timestampNanos);
-    return OK;
-}
-
-status_t WorkDuration::readFromParcel(const Parcel*) {
-    return INVALID_OPERATION;
-}
-
-} // namespace android::os
diff --git a/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
index 759485f..61ab47a 100644
--- a/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
@@ -70,12 +70,14 @@
 
     if (hal == nullptr) {
         ALOGV("Power HAL not available, skipping test...");
+        state.SkipWithMessage("Power HAL unavailable");
         return;
     }
 
     ndk::ScopedAStatus ret = (*hal.*fn)(std::forward<Args1>(args1)...);
     if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
         ALOGV("Power HAL does not support this operation, skipping test...");
+        state.SkipWithMessage("operation unsupported");
         return;
     }
 
@@ -97,6 +99,7 @@
 
     if (hal == nullptr) {
         ALOGV("Power HAL not available, skipping test...");
+        state.SkipWithMessage("Power HAL unavailable");
         return;
     }
 
@@ -109,12 +112,14 @@
 
     if (session == nullptr) {
         ALOGV("Power HAL doesn't support session, skipping test...");
+        state.SkipWithMessage("operation unsupported");
         return;
     }
 
     ndk::ScopedAStatus ret = (*session.*fn)(std::forward<Args1>(args1)...);
     if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
         ALOGV("Power HAL does not support this operation, skipping test...");
+        state.SkipWithMessage("operation unsupported");
         return;
     }
 
@@ -163,6 +168,7 @@
 
     if (hal == nullptr) {
         ALOGV("Power HAL not available, skipping test...");
+        state.SkipWithMessage("Power HAL unavailable");
         return;
     }
 
@@ -170,6 +176,7 @@
             hal->createHintSession(tgid, uid, threadIds, durationNanos, &appSession);
     if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
         ALOGV("Power HAL does not support this operation, skipping test...");
+        state.SkipWithMessage("operation unsupported");
         return;
     }
 
diff --git a/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
index 111b5d7..bcb376b 100644
--- a/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
@@ -50,6 +50,7 @@
 
     if (hal == nullptr) {
         ALOGV("Power HAL HIDL not available, skipping test...");
+        state.SkipWithMessage("Power HAL unavailable");
         return;
     }
 
diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h
deleted file mode 100644
index 99b5b8b..0000000
--- a/services/powermanager/include/android/WorkDuration.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <binder/Parcelable.h>
-#include <math.h>
-
-struct AWorkDuration {};
-
-namespace android::os {
-
-/**
- * C++ Parcelable version of {@link PerformanceHintManager.WorkDuration} that can be used in
- * binder calls.
- * This file needs to be kept in sync with the WorkDuration in
- * frameworks/base/core/java/android/os/WorkDuration.java
- */
-struct WorkDuration : AWorkDuration, android::Parcelable {
-    WorkDuration() = default;
-    ~WorkDuration() = default;
-
-    WorkDuration(int64_t workPeriodStartTimestampNanos, int64_t actualTotalDurationNanos,
-                 int64_t actualCpuDurationNanos, int64_t actualGpuDurationNanos);
-    status_t writeToParcel(Parcel* parcel) const override;
-    status_t readFromParcel(const Parcel* parcel) override;
-
-    inline bool equalsWithoutTimestamp(const WorkDuration& other) const {
-        return workPeriodStartTimestampNanos == other.workPeriodStartTimestampNanos &&
-                actualTotalDurationNanos == other.actualTotalDurationNanos &&
-                actualCpuDurationNanos == other.actualCpuDurationNanos &&
-                actualGpuDurationNanos == other.actualGpuDurationNanos;
-    }
-
-    bool operator==(const WorkDuration& other) const {
-        return timestampNanos == other.timestampNanos && equalsWithoutTimestamp(other);
-    }
-
-    bool operator!=(const WorkDuration& other) const { return !(*this == other); }
-
-    friend std::ostream& operator<<(std::ostream& os, const WorkDuration& workDuration) {
-        os << "{"
-           << "workPeriodStartTimestampNanos: " << workDuration.workPeriodStartTimestampNanos
-           << ", actualTotalDurationNanos: " << workDuration.actualTotalDurationNanos
-           << ", actualCpuDurationNanos: " << workDuration.actualCpuDurationNanos
-           << ", actualGpuDurationNanos: " << workDuration.actualGpuDurationNanos
-           << ", timestampNanos: " << workDuration.timestampNanos << "}";
-        return os;
-    }
-
-    int64_t workPeriodStartTimestampNanos;
-    int64_t actualTotalDurationNanos;
-    int64_t actualCpuDurationNanos;
-    int64_t actualGpuDurationNanos;
-    int64_t timestampNanos;
-};
-
-} // namespace android::os
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index 6fc96c0..a05ce2b 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -37,6 +37,7 @@
         "PowerHalWrapperHidlV1_1Test.cpp",
         "PowerHalWrapperHidlV1_2Test.cpp",
         "PowerHalWrapperHidlV1_3Test.cpp",
+        "PowerHintSessionWrapperTest.cpp",
         "WorkSourceTest.cpp",
     ],
     cflags: [
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index 641ba9f..1589c99 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -29,9 +29,12 @@
 #include <thread>
 
 using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::ChannelConfig;
 using aidl::android::hardware::power::IPower;
 using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::Mode;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionTag;
 using android::binder::Status;
 
 using namespace android;
@@ -53,6 +56,14 @@
                 (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
                  int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
                 (override));
+    MOCK_METHOD(ndk::ScopedAStatus, createHintSessionWithConfig,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, SessionTag tag, SessionConfig* config,
+                 std::shared_ptr<IPowerHintSession>* _aidl_return),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel,
+                (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
@@ -75,6 +86,10 @@
 
 void PowerHalWrapperAidlTest::SetUp() {
     mMockHal = ndk::SharedRefBase::make<StrictMock<MockIPower>>();
+    EXPECT_CALL(*mMockHal, getInterfaceVersion(_)).WillRepeatedly(([](int32_t* ret) {
+        *ret = 5;
+        return ndk::ScopedAStatus::ok();
+    }));
     mWrapper = std::make_unique<AidlHalWrapper>(mMockHal);
     ASSERT_NE(nullptr, mWrapper);
 }
@@ -119,10 +134,12 @@
 }
 
 TEST_F(PowerHalWrapperAidlTest, TestSetBoostUnsupported) {
-    EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
-            .Times(Exactly(1))
-            .WillOnce(DoAll(SetArgPointee<1>(false),
-                            Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
+    EXPECT_CALL(*mMockHal.get(), isBoostSupported(_, _))
+            .Times(Exactly(2))
+            .WillRepeatedly([](Boost, bool* ret) {
+                *ret = false;
+                return ndk::ScopedAStatus::ok();
+            });
 
     auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
     ASSERT_TRUE(result.isUnsupported());
@@ -245,6 +262,23 @@
     ASSERT_TRUE(result.isOk());
 }
 
+TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionWithConfigSuccessful) {
+    std::vector<int> threadIds{gettid()};
+    int32_t tgid = 999;
+    int32_t uid = 1001;
+    int64_t durationNanos = 16666666L;
+    SessionTag tag = SessionTag::OTHER;
+    SessionConfig out;
+    EXPECT_CALL(*mMockHal.get(),
+                createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos),
+                                            Eq(tag), _, _))
+            .Times(Exactly(1))
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+    auto result =
+            mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out);
+    ASSERT_TRUE(result.isOk());
+}
+
 TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionFailed) {
     int32_t tgid = 999;
     int32_t uid = 1001;
@@ -268,3 +302,44 @@
     int64_t rate = result.value();
     ASSERT_GE(0, rate);
 }
+
+TEST_F(PowerHalWrapperAidlTest, TestSessionChannel) {
+    int32_t tgid = 999;
+    int32_t uid = 1001;
+    EXPECT_CALL(*mMockHal.get(), getSessionChannel(Eq(tgid), Eq(uid), _))
+            .Times(Exactly(1))
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), closeSessionChannel(Eq(tgid), Eq(uid)))
+            .Times(Exactly(1))
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+    auto createResult = mWrapper->getSessionChannel(tgid, uid);
+    ASSERT_TRUE(createResult.isOk());
+    auto closeResult = mWrapper->closeSessionChannel(tgid, uid);
+    ASSERT_TRUE(closeResult.isOk());
+}
+
+TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionWithConfigUnsupported) {
+    std::vector<int> threadIds{gettid()};
+    int32_t tgid = 999;
+    int32_t uid = 1001;
+    int64_t durationNanos = 16666666L;
+    SessionTag tag = SessionTag::OTHER;
+    SessionConfig out;
+    EXPECT_CALL(*mMockHal.get(),
+                createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos),
+                                            Eq(tag), _, _))
+            .Times(1)
+            .WillOnce(Return(testing::ByMove(
+                    ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION))));
+    auto result =
+            mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out);
+    ASSERT_TRUE(result.isUnsupported());
+    Mock::VerifyAndClearExpectations(mMockHal.get());
+    EXPECT_CALL(*mMockHal.get(),
+                createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos),
+                                            Eq(tag), _, _))
+            .WillOnce(Return(
+                    testing::ByMove(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION))));
+    result = mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out);
+    ASSERT_TRUE(result.isUnsupported());
+}
diff --git a/services/powermanager/tests/PowerHintSessionWrapperTest.cpp b/services/powermanager/tests/PowerHintSessionWrapperTest.cpp
new file mode 100644
index 0000000..7743fa4
--- /dev/null
+++ b/services/powermanager/tests/PowerHintSessionWrapperTest.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2024 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 <aidl/android/hardware/power/IPowerHintSession.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using aidl::android::hardware::power::IPowerHintSession;
+using android::power::PowerHintSessionWrapper;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+class MockIPowerHintSession : public IPowerHintSession {
+public:
+    MockIPowerHintSession() = default;
+    MOCK_METHOD(::ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t in_targetDurationNanos),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, reportActualWorkDuration,
+                (const std::vector<::aidl::android::hardware::power::WorkDuration>& in_durations),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, pause, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, resume, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, close, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, sendHint,
+                (::aidl::android::hardware::power::SessionHint in_hint), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setThreads, (const std::vector<int32_t>& in_threadIds),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setMode,
+                (::aidl::android::hardware::power::SessionMode in_type, bool in_enabled),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getSessionConfig,
+                (::aidl::android::hardware::power::SessionConfig * _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override));
+    MOCK_METHOD(::ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+};
+
+class PowerHintSessionWrapperTest : public Test {
+public:
+    void SetUp() override;
+
+protected:
+    std::shared_ptr<NiceMock<MockIPowerHintSession>> mMockSession = nullptr;
+    std::unique_ptr<PowerHintSessionWrapper> mSession = nullptr;
+};
+
+void PowerHintSessionWrapperTest::SetUp() {
+    mMockSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
+    EXPECT_CALL(*mMockSession, getInterfaceVersion(_)).WillRepeatedly(([](int32_t* ret) {
+        *ret = 5;
+        return ndk::ScopedAStatus::ok();
+    }));
+    mSession = std::make_unique<PowerHintSessionWrapper>(mMockSession);
+    ASSERT_NE(nullptr, mSession);
+}
+
+TEST_F(PowerHintSessionWrapperTest, updateTargetWorkDuration) {
+    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1000000000))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->updateTargetWorkDuration(1000000000);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, reportActualWorkDuration) {
+    EXPECT_CALL(*mMockSession.get(),
+                reportActualWorkDuration(
+                        std::vector<::aidl::android::hardware::power::WorkDuration>()))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->reportActualWorkDuration(
+            std::vector<::aidl::android::hardware::power::WorkDuration>());
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, pause) {
+    EXPECT_CALL(*mMockSession.get(), pause()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->pause();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, resume) {
+    EXPECT_CALL(*mMockSession.get(), resume()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->resume();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, close) {
+    EXPECT_CALL(*mMockSession.get(), close()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->close();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, sendHint) {
+    EXPECT_CALL(*mMockSession.get(),
+                sendHint(::aidl::android::hardware::power::SessionHint::CPU_LOAD_UP))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->sendHint(::aidl::android::hardware::power::SessionHint::CPU_LOAD_UP);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, setThreads) {
+    EXPECT_CALL(*mMockSession.get(), setThreads(_)).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->setThreads(std::vector<int32_t>{gettid()});
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, setMode) {
+    EXPECT_CALL(*mMockSession.get(),
+                setMode(::aidl::android::hardware::power::SessionMode::POWER_EFFICIENCY, true))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->setMode(::aidl::android::hardware::power::SessionMode::POWER_EFFICIENCY,
+                                    true);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, getSessionConfig) {
+    EXPECT_CALL(*mMockSession.get(), getSessionConfig(_))
+            .WillOnce(DoAll(SetArgPointee<0>(
+                                    aidl::android::hardware::power::SessionConfig{.id = 12L}),
+                            Return(ndk::ScopedAStatus::ok())));
+    auto status = mSession->getSessionConfig();
+    ASSERT_TRUE(status.isOk());
+}
diff --git a/services/sensorservice/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
index e60db93..91c2d1f 100644
--- a/services/sensorservice/AidlSensorHalWrapper.cpp
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -178,6 +178,11 @@
         if ((eventFlagState & asBaseType(INTERNAL_WAKE)) && mReconnecting) {
             ALOGD("Event FMQ internal wake, returning from poll with no events");
             return DEAD_OBJECT;
+        } else if ((eventFlagState & asBaseType(INTERNAL_WAKE)) && mInHalBypassMode &&
+                   availableEvents == 0) {
+            ALOGD("Event FMQ internal wake due to HAL Bypass Mode, returning from poll with no "
+                  "events");
+            return OK;
         }
     }
 
@@ -221,6 +226,17 @@
 
 status_t AidlSensorHalWrapper::setOperationMode(SensorService::Mode mode) {
     if (mSensors == nullptr) return NO_INIT;
+    if (mode == SensorService::Mode::HAL_BYPASS_REPLAY_DATA_INJECTION) {
+        if (!mInHalBypassMode) {
+            mInHalBypassMode = true;
+            mEventQueueFlag->wake(asBaseType(INTERNAL_WAKE));
+        }
+        return OK;
+    } else {
+        if (mInHalBypassMode) {
+            mInHalBypassMode = false;
+        }
+    }
     return convertToStatus(mSensors->setOperationMode(static_cast<ISensors::OperationMode>(mode)));
 }
 
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 11c56a8..afaf0ae 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -7,6 +7,19 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "sensorservice_flags",
+    package: "com.android.frameworks.sensorservice.flags",
+    container: "system",
+    srcs: ["senserservice_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "sensorservice_flags_c_lib",
+    aconfig_declarations: "sensorservice_flags",
+    host_supported: true,
+}
+
 cc_library {
     name: "libsensorservice",
 
@@ -70,6 +83,7 @@
         "android.hardware.sensors@2.1",
         "android.hardware.common-V2-ndk",
         "android.hardware.common.fmq-V1-ndk",
+        "server_configurable_flags",
     ],
 
     static_libs: [
@@ -77,6 +91,7 @@
         "android.hardware.sensors@1.0-convert",
         "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V2-ndk",
+        "sensorservice_flags_c_lib",
     ],
 
     generated_headers: ["framework-cppstream-protos"],
diff --git a/services/sensorservice/HidlSensorHalWrapper.cpp b/services/sensorservice/HidlSensorHalWrapper.cpp
index c55c9b4..8c867bd 100644
--- a/services/sensorservice/HidlSensorHalWrapper.cpp
+++ b/services/sensorservice/HidlSensorHalWrapper.cpp
@@ -203,6 +203,11 @@
         if ((eventFlagState & asBaseType(INTERNAL_WAKE)) && mReconnecting) {
             ALOGD("Event FMQ internal wake, returning from poll with no events");
             return DEAD_OBJECT;
+        } else if ((eventFlagState & asBaseType(INTERNAL_WAKE)) && mInHalBypassMode &&
+                   availableEvents == 0) {
+            ALOGD("Event FMQ internal wake due to HAL Bypass Mode, returning from poll with no "
+                  "events");
+            return OK;
         }
     }
 
@@ -251,6 +256,17 @@
 
 status_t HidlSensorHalWrapper::setOperationMode(SensorService::Mode mode) {
     if (mSensors == nullptr) return NO_INIT;
+    if (mode == SensorService::Mode::HAL_BYPASS_REPLAY_DATA_INJECTION) {
+        if (!mInHalBypassMode) {
+            mInHalBypassMode = true;
+            mEventQueueFlag->wake(asBaseType(INTERNAL_WAKE));
+        }
+        return OK;
+    } else {
+        if (mInHalBypassMode) {
+            mInHalBypassMode = false;
+        }
+    }
     return checkReturnAndGetStatus(
             mSensors->setOperationMode(static_cast<hardware::sensors::V1_0::OperationMode>(mode)));
 }
diff --git a/services/sensorservice/ISensorHalWrapper.h b/services/sensorservice/ISensorHalWrapper.h
index 3d33540..891dfe5 100644
--- a/services/sensorservice/ISensorHalWrapper.h
+++ b/services/sensorservice/ISensorHalWrapper.h
@@ -97,6 +97,8 @@
     virtual void writeWakeLockHandled(uint32_t count) = 0;
 
     std::atomic_bool mReconnecting = false;
+
+    std::atomic_bool mInHalBypassMode = false;
 };
 
 } // namespace android
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index dd83fde..9c4d1ac 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -16,15 +16,9 @@
 
 #include "SensorDevice.h"
 
-#include "android/hardware/sensors/2.0/types.h"
-#include "android/hardware/sensors/2.1/types.h"
-#include "convertV2_1.h"
-
-#include "AidlSensorHalWrapper.h"
-#include "HidlSensorHalWrapper.h"
-
 #include <android-base/logging.h>
 #include <android/util/ProtoOutputStream.h>
+#include <com_android_frameworks_sensorservice_flags.h>
 #include <cutils/atomic.h>
 #include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
 #include <hardware/sensors-base.h>
@@ -35,13 +29,20 @@
 
 #include <chrono>
 #include <cinttypes>
-#include <cstddef>
-#include <thread>
-#include <mutex>
 #include <condition_variable>
+#include <cstddef>
+#include <mutex>
+#include <thread>
+
+#include "AidlSensorHalWrapper.h"
+#include "HidlSensorHalWrapper.h"
+#include "android/hardware/sensors/2.0/types.h"
+#include "android/hardware/sensors/2.1/types.h"
+#include "convertV2_1.h"
 
 using namespace android::hardware::sensors;
 using android::util::ProtoOutputStream;
+namespace sensorservice_flags = com::android::frameworks::sensorservice::flags;
 
 namespace android {
 // ---------------------------------------------------------------------------
@@ -166,6 +167,9 @@
 
     mActivationCount.clear();
     mSensorList.clear();
+    if (sensorservice_flags::dynamic_sensor_hal_reconnect_handling()) {
+        mConnectedDynamicSensors.clear();
+    }
 
     if (mHalWrapper->connect(this)) {
         initializeSensorList();
@@ -340,6 +344,15 @@
     }
 }
 
+std::vector<int32_t> SensorDevice::getDynamicSensorHandles() {
+    std::vector<int32_t> sensorHandles;
+    std::lock_guard<std::mutex> lock(mDynamicSensorsMutex);
+    for (auto& sensors : mConnectedDynamicSensors) {
+        sensorHandles.push_back(sensors.first);
+    }
+    return sensorHandles;
+}
+
 ssize_t SensorDevice::getSensorList(sensor_t const** list) {
     *list = &mSensorList[0];
 
@@ -416,8 +429,19 @@
 }
 
 void SensorDevice::onDynamicSensorsDisconnected(
-        const std::vector<int32_t>& /* dynamicSensorHandlesRemoved */) {
-    // TODO: Currently dynamic sensors do not seem to be removed
+        const std::vector<int32_t>& /*dynamicSensorHandlesRemoved*/) {
+    // This function is currently a no-op has removing data in mConnectedDynamicSensors here will
+    // cause a race condition between when this callback is invoked and when the dynamic sensor meta
+    // event is processed by polling. The clean up should only happen after processing the meta
+    // event. See the call stack of cleanupDisconnectedDynamicSensor.
+}
+
+void SensorDevice::cleanupDisconnectedDynamicSensor(int handle) {
+    std::lock_guard<std::mutex> lock(mDynamicSensorsMutex);
+    auto it = mConnectedDynamicSensors.find(handle);
+    if (it != mConnectedDynamicSensors.end()) {
+        mConnectedDynamicSensors.erase(it);
+    }
 }
 
 void SensorDevice::writeWakeLockHandled(uint32_t count) {
@@ -483,12 +507,16 @@
     } else {
         ALOGD_IF(DEBUG_CONNECTIONS, "disable index=%zd", info.batchParams.indexOfKey(ident));
 
-        // If a connected dynamic sensor is deactivated, remove it from the
-        // dictionary.
+        // TODO(b/316958439): Remove these line after
+        // sensor_device_on_dynamic_sensor_disconnected is ramped up. Bounded
+        // here since this function is coupled with
+        // dynamic_sensors_hal_disconnect_dynamic_sensor flag. If a connected
+        // dynamic sensor is deactivated, remove it from the dictionary.
         auto it = mConnectedDynamicSensors.find(handle);
         if (it != mConnectedDynamicSensors.end()) {
-            mConnectedDynamicSensors.erase(it);
+          mConnectedDynamicSensors.erase(it);
         }
+        // End of TODO(b/316958439)
 
         if (info.removeBatchParamsForIdent(ident) >= 0) {
             if (info.numActiveClients() == 0) {
@@ -788,7 +816,6 @@
             }
             mInHalBypassMode = true;
         }
-        return OK;
     } else {
         if (mInHalBypassMode) {
             // We are transitioning out of HAL Bypass mode. We need to notify the reader thread
diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h
index f127c0f..b7b04b5 100644
--- a/services/sensorservice/SensorDevice.h
+++ b/services/sensorservice/SensorDevice.h
@@ -60,7 +60,17 @@
 
     ssize_t getSensorList(sensor_t const** list);
 
+    std::vector<int32_t> getDynamicSensorHandles();
+
     void handleDynamicSensorConnection(int handle, bool connected);
+    /**
+     * Removes handle from connected dynamic sensor list. Note that this method must be called after
+     * SensorService has done using sensor data.
+     *
+     * @param handle of the disconnected dynamic sensor.
+     */
+    void cleanupDisconnectedDynamicSensor(int handle);
+
     status_t initCheck() const;
     int getHalDeviceVersion() const;
 
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index dc86577..3446f58 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -461,16 +461,18 @@
         // is pre-Q, still permit delivering events to the app even if permission isn't granted
         // (since this permission was only introduced in Q)
         if ((event.type == SENSOR_TYPE_STEP_COUNTER || event.type == SENSOR_TYPE_STEP_DETECTOR) &&
-                mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+            mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+            success = true;
+        } else if (mUid == AID_SYSTEM) {
+            // Allow access if it is requested from system.
             success = true;
         } else {
             int32_t sensorHandle = event.sensor;
             String16 noteMsg("Sensor event (");
             noteMsg.append(String16(mService->getSensorStringType(sensorHandle)));
             noteMsg.append(String16(")"));
-            int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid,
-                                                                mOpPackageName, mAttributionTag,
-                                                                noteMsg);
+            int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid, mOpPackageName,
+                                                                mAttributionTag, noteMsg);
             success = (appOpMode == AppOpsManager::MODE_ALLOWED);
         }
     }
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 85043c9..31b7f88 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -13,6 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include "SensorService.h"
+
 #include <aidl/android/hardware/sensors/ISensors.h>
 #include <android-base/strings.h>
 #include <android/content/pm/IPackageManagerNative.h>
@@ -22,20 +24,36 @@
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
 #include <binder/PermissionController.h>
+#include <com_android_frameworks_sensorservice_flags.h>
 #include <cutils/ashmem.h>
 #include <cutils/misc.h>
 #include <cutils/properties.h>
 #include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
 #include <hardware/sensors.h>
 #include <hardware_legacy/power.h>
+#include <inttypes.h>
 #include <log/log.h>
+#include <math.h>
 #include <openssl/digest.h>
 #include <openssl/hmac.h>
 #include <openssl/rand.h>
+#include <private/android_filesystem_config.h>
+#include <sched.h>
 #include <sensor/SensorEventQueue.h>
 #include <sensorprivacy/SensorPrivacyManager.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 #include <utils/SystemClock.h>
 
+#include <condition_variable>
+#include <ctime>
+#include <future>
+#include <mutex>
+#include <string>
+
 #include "BatteryService.h"
 #include "CorrectedGyroSensor.h"
 #include "GravitySensor.h"
@@ -43,35 +61,17 @@
 #include "LinearAccelerationSensor.h"
 #include "OrientationSensor.h"
 #include "RotationVectorSensor.h"
-#include "SensorFusion.h"
-#include "SensorInterface.h"
-
-#include "SensorService.h"
 #include "SensorDirectConnection.h"
 #include "SensorEventAckReceiver.h"
 #include "SensorEventConnection.h"
+#include "SensorFusion.h"
+#include "SensorInterface.h"
 #include "SensorRecord.h"
 #include "SensorRegistrationInfo.h"
 #include "SensorServiceUtils.h"
 
-#include <inttypes.h>
-#include <math.h>
-#include <sched.h>
-#include <stdint.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <condition_variable>
-#include <ctime>
-#include <future>
-#include <mutex>
-#include <string>
-
-#include <private/android_filesystem_config.h>
-
 using namespace std::chrono_literals;
+namespace sensorservice_flags = com::android::frameworks::sensorservice::flags;
 
 namespace android {
 // ---------------------------------------------------------------------------
@@ -335,6 +335,11 @@
                     case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED:
                         hasGyroUncalibrated = true;
                         break;
+                    case SENSOR_TYPE_DYNAMIC_SENSOR_META:
+                        if (sensorservice_flags::dynamic_sensor_hal_reconnect_handling()) {
+                            mDynamicMetaSensorHandle = list[i].handle;
+                        }
+                      break;
                     case SENSOR_TYPE_GRAVITY:
                     case SENSOR_TYPE_LINEAR_ACCELERATION:
                     case SENSOR_TYPE_ROTATION_VECTOR:
@@ -1055,6 +1060,68 @@
    }
 }
 
+void SensorService::sendEventsToAllClients(
+    const std::vector<sp<SensorEventConnection>>& activeConnections,
+    ssize_t count) {
+   // Send our events to clients. Check the state of wake lock for each client
+   // and release the lock if none of the clients need it.
+   bool needsWakeLock = false;
+   for (const sp<SensorEventConnection>& connection : activeConnections) {
+       connection->sendEvents(mSensorEventBuffer, count, mSensorEventScratch,
+                              mMapFlushEventsToConnections);
+       needsWakeLock |= connection->needsWakeLock();
+       // If the connection has one-shot sensors, it may be cleaned up after
+       // first trigger. Early check for one-shot sensors.
+       if (connection->hasOneShotSensors()) {
+           cleanupAutoDisabledSensorLocked(connection, mSensorEventBuffer, count);
+       }
+   }
+
+   if (mWakeLockAcquired && !needsWakeLock) {
+        setWakeLockAcquiredLocked(false);
+   }
+}
+
+void SensorService::disconnectDynamicSensor(
+    int handle,
+    const std::vector<sp<SensorEventConnection>>& activeConnections) {
+   ALOGI("Dynamic sensor handle 0x%x disconnected", handle);
+   SensorDevice::getInstance().handleDynamicSensorConnection(
+       handle, false /*connected*/);
+   if (!unregisterDynamicSensorLocked(handle)) {
+        ALOGE("Dynamic sensor release error.");
+   }
+   for (const sp<SensorEventConnection>& connection : activeConnections) {
+        connection->removeSensor(handle);
+   }
+}
+
+void SensorService::handleDeviceReconnection(SensorDevice& device) {
+    if (sensorservice_flags::dynamic_sensor_hal_reconnect_handling()) {
+        const std::vector<sp<SensorEventConnection>> activeConnections =
+                mConnectionHolder.lock(mLock).getActiveConnections();
+
+        for (int32_t handle : device.getDynamicSensorHandles()) {
+            if (mDynamicMetaSensorHandle.has_value()) {
+                // Sending one event at a time to prevent the number of handle is more than the
+                // buffer can hold.
+                mSensorEventBuffer[0].type = SENSOR_TYPE_DYNAMIC_SENSOR_META;
+                mSensorEventBuffer[0].sensor = *mDynamicMetaSensorHandle;
+                mSensorEventBuffer[0].dynamic_sensor_meta.connected = false;
+                mSensorEventBuffer[0].dynamic_sensor_meta.handle = handle;
+                mMapFlushEventsToConnections[0] = nullptr;
+
+                disconnectDynamicSensor(handle, activeConnections);
+                sendEventsToAllClients(activeConnections, 1);
+            } else {
+                ALOGE("Failed to find mDynamicMetaSensorHandle during init.");
+                break;
+            }
+        }
+    }
+    device.reconnect();
+}
+
 bool SensorService::threadLoop() {
     ALOGD("nuSensorService thread starting...");
 
@@ -1071,8 +1138,8 @@
     do {
         ssize_t count = device.poll(mSensorEventBuffer, numEventMax);
         if (count < 0) {
-            if(count == DEAD_OBJECT && device.isReconnecting()) {
-                device.reconnect();
+            if (count == DEAD_OBJECT && device.isReconnecting()) {
+                handleDeviceReconnection(device);
                 continue;
             } else {
                 ALOGE("sensor poll failed (%s)", strerror(-count));
@@ -1176,7 +1243,6 @@
                     rec->removeFirstPendingFlushConnection();
                 }
             }
-
             // handle dynamic sensor meta events, process registration and unregistration of dynamic
             // sensor based on content of event.
             if (mSensorEventBuffer[i].type == SENSOR_TYPE_DYNAMIC_SENSOR_META) {
@@ -1206,15 +1272,10 @@
                     }
                 } else {
                     int handle = mSensorEventBuffer[i].dynamic_sensor_meta.handle;
-                    ALOGI("Dynamic sensor handle 0x%x disconnected", handle);
-
-                    device.handleDynamicSensorConnection(handle, false /*connected*/);
-                    if (!unregisterDynamicSensorLocked(handle)) {
-                        ALOGE("Dynamic sensor release error.");
-                    }
-
-                    for (const sp<SensorEventConnection>& connection : activeConnections) {
-                        connection->removeSensor(handle);
+                    disconnectDynamicSensor(handle, activeConnections);
+                    if (sensorservice_flags::
+                            sensor_service_clear_dynamic_sensor_data_at_the_end()) {
+                      device.cleanupDisconnectedDynamicSensor(handle);
                     }
                 }
             }
@@ -1222,21 +1283,7 @@
 
         // Send our events to clients. Check the state of wake lock for each client and release the
         // lock if none of the clients need it.
-        bool needsWakeLock = false;
-        for (const sp<SensorEventConnection>& connection : activeConnections) {
-            connection->sendEvents(mSensorEventBuffer, count, mSensorEventScratch,
-                                   mMapFlushEventsToConnections);
-            needsWakeLock |= connection->needsWakeLock();
-            // If the connection has one-shot sensors, it may be cleaned up after first trigger.
-            // Early check for one-shot sensors.
-            if (connection->hasOneShotSensors()) {
-                cleanupAutoDisabledSensorLocked(connection, mSensorEventBuffer, count);
-            }
-        }
-
-        if (mWakeLockAcquired && !needsWakeLock) {
-            setWakeLockAcquiredLocked(false);
-        }
+        sendEventsToAllClients(activeConnections, count);
     } while (!Thread::exitPending());
 
     ALOGW("Exiting SensorService::threadLoop => aborting...");
@@ -2259,11 +2306,16 @@
         // requirement to hold the AR permission to access Step Counter and Step Detector events
         // was introduced.
         canAccess = true;
+    } else if (IPCThreadState::self()->getCallingUid() == AID_SYSTEM) {
+        // Allow access if it is requested from system.
+        canAccess = true;
     } else if (hasPermissionForSensor(sensor)) {
-        // Ensure that the AppOp is allowed, or that there is no necessary app op for the sensor
+        // Ensure that the AppOp is allowed, or that there is no necessary app op
+        // for the sensor
         if (opCode >= 0) {
-            const int32_t appOpMode = sAppOpsManager.checkOp(opCode,
-                    IPCThreadState::self()->getCallingUid(), opPackageName);
+            const int32_t appOpMode =
+                    sAppOpsManager.checkOp(opCode, IPCThreadState::self()->getCallingUid(),
+                                           opPackageName);
             canAccess = (appOpMode == AppOpsManager::MODE_ALLOWED);
         } else {
             canAccess = true;
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index b643f6b..bd54d24 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -17,9 +17,6 @@
 #ifndef ANDROID_SENSOR_SERVICE_H
 #define ANDROID_SENSOR_SERVICE_H
 
-#include "SensorList.h"
-#include "RecentEventLogger.h"
-
 #include <android-base/macros.h>
 #include <binder/AppOpsManager.h>
 #include <binder/BinderService.h>
@@ -27,11 +24,11 @@
 #include <cutils/compiler.h>
 #include <cutils/multiuser.h>
 #include <private/android_filesystem_config.h>
-#include <sensor/ISensorServer.h>
 #include <sensor/ISensorEventConnection.h>
+#include <sensor/ISensorServer.h>
 #include <sensor/Sensor.h>
-#include "android/hardware/BnSensorPrivacyListener.h"
-
+#include <stdint.h>
+#include <sys/types.h>
 #include <utils/AndroidThreads.h>
 #include <utils/KeyedVector.h>
 #include <utils/Looper.h>
@@ -40,8 +37,6 @@
 #include <utils/Vector.h>
 #include <utils/threads.h>
 
-#include <stdint.h>
-#include <sys/types.h>
 #include <condition_variable>
 #include <mutex>
 #include <queue>
@@ -49,6 +44,10 @@
 #include <unordered_set>
 #include <vector>
 
+#include "RecentEventLogger.h"
+#include "SensorList.h"
+#include "android/hardware/BnSensorPrivacyListener.h"
+
 #if __clang__
 // Clang warns about SensorEventConnection::dump hiding BBinder::dump. The cause isn't fixable
 // without changing the API, so let's tell clang this is indeed intentional.
@@ -57,7 +56,7 @@
 
 // ---------------------------------------------------------------------------
 #define IGNORE_HARDWARE_FUSION  false
-#define DEBUG_CONNECTIONS   false
+#define DEBUG_CONNECTIONS false
 // Max size is 100 KB which is enough to accept a batch of about 1000 events.
 #define MAX_SOCKET_BUFFER_SIZE_BATCHED (100 * 1024)
 // For older HALs which don't support batching, use a smaller socket buffer size.
@@ -341,6 +340,12 @@
             binder::Status onSensorPrivacyChanged(int toggleType, int sensor,
                                                   bool enabled);
 
+            // This callback is used for additional automotive-specific state for sensor privacy
+            // such as ENABLED_EXCEPT_ALLOWLISTED_APPS. The newly defined states will only be valid
+            // for camera privacy on automotive devices. onSensorPrivacyChanged() will still be
+            // invoked whenever the enabled status of a toggle changes.
+            binder::Status onSensorPrivacyStateChanged(int, int, int) {return binder::Status::ok();}
+
         protected:
             std::atomic_bool mSensorPrivacyEnabled;
             wp<SensorService> mService;
@@ -453,6 +458,11 @@
     // Send events from the event cache for this particular connection.
     void sendEventsFromCache(const sp<SensorEventConnection>& connection);
 
+    // Send all events in the buffer to all clients.
+    void sendEventsToAllClients(
+        const std::vector<sp<SensorEventConnection>>& activeConnections,
+        ssize_t count);
+
     // If SensorService is operating in RESTRICTED mode, only select whitelisted packages are
     // allowed to register for or call flush on sensors. Typically only cts test packages are
     // allowed.
@@ -516,6 +526,14 @@
 
     bool isInjectionMode(int mode);
 
+    void handleDeviceReconnection(SensorDevice& device);
+
+    // Removes a connected dynamic sensor and send the corresponding event to
+    // all connections.
+    void disconnectDynamicSensor(
+        int handle,
+        const std::vector<sp<SensorEventConnection>>& activeConnections);
+
     static inline bool isAudioServerOrSystemServerUid(uid_t uid) {
         return multiuser_get_app_id(uid) == AID_SYSTEM || uid == AID_AUDIOSERVER;
     }
@@ -583,6 +601,10 @@
     bool mLastReportedProxIsActive;
     // Listeners subscribed to receive updates on the proximity sensor active state.
     std::vector<sp<ProximityActiveListener>> mProximityActiveListeners;
+
+    // Stores the handle of the dynamic_meta sensor to send clean up event once
+    // HAL crashes.
+    std::optional<int> mDynamicMetaSensorHandle;
 };
 
 } // namespace android
diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig
new file mode 100644
index 0000000..5b499a8
--- /dev/null
+++ b/services/sensorservice/senserservice_flags.aconfig
@@ -0,0 +1,30 @@
+package: "com.android.frameworks.sensorservice.flags"
+container: "system"
+
+flag {
+  name: "dynamic_sensor_hal_reconnect_handling"
+  namespace: "sensors"
+  description: "This flag controls if the dynamic sensor data will be clean up after HAL is disconnected."
+  bug: "307782607"
+}
+
+flag {
+  name: "sensor_device_on_dynamic_sensor_disconnected"
+  namespace: "sensors"
+  description: "This flag controls if the callback onDynamicSensorsDisconnected is implemented or not."
+  bug: "316958439"
+}
+
+flag {
+  name: "sensor_event_connection_send_event_require_nonnull_scratch"
+  namespace: "sensors"
+  description: "This flag controls we allow to pass in nullptr as scratch in SensorEventConnection::sendEvents()"
+  bug: "339306599"
+}
+
+flag {
+  name: "sensor_service_clear_dynamic_sensor_data_at_the_end"
+  namespace: "sensors"
+  description: "When this flag is enabled, sensor service will only erase dynamic sensor data at the end of the threadLoop to prevent race condition."
+  bug: "329020894"
+}
\ No newline at end of file
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index dcef9a3..8ca796e 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -5,13 +5,17 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 aconfig_declarations {
     name: "surfaceflinger_flags",
     package: "com.android.graphics.surfaceflinger.flags",
     container: "system",
-    srcs: ["surfaceflinger_flags.aconfig"],
+    srcs: [
+        "surfaceflinger_flags.aconfig",
+        "surfaceflinger_flags_new.aconfig",
+    ],
 }
 
 cc_aconfig_library {
@@ -41,6 +45,7 @@
         "android.hardware.power-ndk_shared",
         "librenderengine_deps",
         "libtimestats_deps",
+        "libsurfaceflinger_common_deps",
         "surfaceflinger_defaults",
     ],
     cflags: [
@@ -78,12 +83,11 @@
         "libprotobuf-cpp-lite",
         "libsync",
         "libui",
-        "libinput",
         "libutils",
         "libSurfaceFlingerProp",
-        "server_configurable_flags",
     ],
     static_libs: [
+        "iinputflinger_aidl_lib_static",
         "libaidlcommonsupport",
         "libcompositionengine",
         "libframetimeline",
@@ -94,10 +98,8 @@
         "libscheduler",
         "libserviceutils",
         "libshaders",
-        "libsurfaceflinger_common",
         "libtimestats",
         "libtonemap",
-        "libsurfaceflingerflags",
     ],
     header_libs: [
         "android.hardware.graphics.composer@2.1-command-buffer",
@@ -156,6 +158,7 @@
         "BackgroundExecutor.cpp",
         "Client.cpp",
         "ClientCache.cpp",
+        "Display/DisplayModeController.cpp",
         "Display/DisplaySnapshot.cpp",
         "DisplayDevice.cpp",
         "DisplayHardware/AidlComposerHal.cpp",
@@ -208,7 +211,6 @@
         "Scheduler/VsyncModulator.cpp",
         "Scheduler/VsyncSchedule.cpp",
         "ScreenCaptureOutput.cpp",
-        "StartPropertySetThread.cpp",
         "SurfaceFlinger.cpp",
         "SurfaceFlingerDefaultFactory.cpp",
         "Tracing/LayerDataSource.cpp",
diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp
index 5a1ec6f..3cef875 100644
--- a/services/surfaceflinger/BackgroundExecutor.cpp
+++ b/services/surfaceflinger/BackgroundExecutor.cpp
@@ -19,6 +19,9 @@
 #define LOG_TAG "BackgroundExecutor"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <processgroup/sched_policy.h>
+#include <pthread.h>
+#include <sched.h>
 #include <utils/Log.h>
 #include <mutex>
 
@@ -26,14 +29,24 @@
 
 namespace android {
 
-ANDROID_SINGLETON_STATIC_INSTANCE(BackgroundExecutor);
+namespace {
 
-BackgroundExecutor::BackgroundExecutor() : Singleton<BackgroundExecutor>() {
+void set_thread_priority(bool highPriority) {
+    set_sched_policy(0, highPriority ? SP_FOREGROUND : SP_BACKGROUND);
+    struct sched_param param = {0};
+    param.sched_priority = highPriority ? 2 : 0 /* must be 0 for non-RT */;
+    sched_setscheduler(gettid(), highPriority ? SCHED_FIFO : SCHED_NORMAL, &param);
+}
+
+} // anonymous namespace
+
+BackgroundExecutor::BackgroundExecutor(bool highPriority) {
     // mSemaphore must be initialized before any calls to
     // BackgroundExecutor::sendCallbacks. For this reason, we initialize it
     // within the constructor instead of within mThread.
     LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed");
-    mThread = std::thread([&]() {
+    mThread = std::thread([&, highPriority]() {
+        set_thread_priority(highPriority);
         while (!mDone) {
             LOG_ALWAYS_FATAL_IF(sem_wait(&mSemaphore), "sem_wait failed (%d)", errno);
             auto callbacks = mCallbacksQueue.pop();
@@ -45,6 +58,11 @@
             }
         }
     });
+    if (highPriority) {
+        pthread_setname_np(mThread.native_handle(), "BckgrndExec HP");
+    } else {
+        pthread_setname_np(mThread.native_handle(), "BckgrndExec LP");
+    }
 }
 
 BackgroundExecutor::~BackgroundExecutor() {
diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h
index 66b7d7a..1b5fadd 100644
--- a/services/surfaceflinger/BackgroundExecutor.h
+++ b/services/surfaceflinger/BackgroundExecutor.h
@@ -18,7 +18,6 @@
 
 #include <ftl/small_vector.h>
 #include <semaphore.h>
-#include <utils/Singleton.h>
 #include <thread>
 
 #include "LocklessQueue.h"
@@ -26,10 +25,20 @@
 namespace android {
 
 // Executes tasks off the main thread.
-class BackgroundExecutor : public Singleton<BackgroundExecutor> {
+class BackgroundExecutor {
 public:
-    BackgroundExecutor();
     ~BackgroundExecutor();
+
+    static BackgroundExecutor& getInstance() {
+        static BackgroundExecutor instance(true);
+        return instance;
+    }
+
+    static BackgroundExecutor& getLowPriorityInstance() {
+        static BackgroundExecutor instance(false);
+        return instance;
+    }
+
     using Callbacks = ftl::SmallVector<std::function<void()>, 10>;
     // Queues callbacks onto a work queue to be executed by a background thread.
     // This is safe to call from multiple threads.
@@ -37,6 +46,8 @@
     void flushQueue();
 
 private:
+    BackgroundExecutor(bool highPriority);
+
     sem_t mSemaphore;
     std::atomic_bool mDone = false;
 
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 0a70d42..7fa58df 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_defaults {
@@ -38,7 +39,6 @@
         "libSurfaceFlingerProp",
         "libui",
         "libutils",
-        "server_configurable_flags",
     ],
     static_libs: [
         "liblayers_proto",
@@ -90,24 +90,23 @@
 
 cc_library {
     name: "libcompositionengine",
-    defaults: ["libcompositionengine_defaults"],
-    static_libs: [
-        "libsurfaceflinger_common",
-        "libsurfaceflingerflags",
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_deps",
     ],
     srcs: [
         ":libcompositionengine_sources",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
-    shared_libs: [
-        "server_configurable_flags",
-    ],
 }
 
 cc_library {
     name: "libcompositionengine_mocks",
-    defaults: ["libcompositionengine_defaults"],
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         "mock/CompositionEngine.cpp",
         "mock/Display.cpp",
@@ -123,11 +122,6 @@
         "libgtest",
         "libgmock",
         "libcompositionengine",
-        "libsurfaceflinger_common_test",
-        "libsurfaceflingerflags_test",
-    ],
-    shared_libs: [
-        "server_configurable_flags",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
@@ -140,7 +134,10 @@
         "frameworks/native/services/surfaceflinger/common/include",
         "frameworks/native/services/surfaceflinger/tests/unittests",
     ],
-    defaults: ["libcompositionengine_defaults"],
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         ":libcompositionengine_sources",
         "tests/planner/CachedSetTest.cpp",
@@ -166,14 +163,11 @@
         "librenderengine_mocks",
         "libgmock",
         "libgtest",
-        "libsurfaceflinger_common_test",
-        "libsurfaceflingerflags_test",
     ],
     shared_libs: [
         // For some reason, libvulkan isn't picked up from librenderengine
         // Probably ASAN related?
         "libvulkan",
-        "server_configurable_flags",
     ],
     sanitize: {
         hwaddress: true,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index 7c10fa5..e32cc02 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -73,6 +73,9 @@
     // TODO(b/121291683): These will become private/internal
     virtual void preComposition(CompositionRefreshArgs&) = 0;
 
+    // Resolves any unfulfilled promises for release fences
+    virtual void postComposition(CompositionRefreshArgs&) = 0;
+
     virtual FeatureFlags getFeatureFlags() const = 0;
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 1a235e9..dd0f985 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -19,11 +19,13 @@
 #include <chrono>
 #include <optional>
 #include <vector>
+#include "utils/Timers.h"
 
 #include <compositionengine/Display.h>
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/OutputColorSetting.h>
 #include <math/mat4.h>
+#include <scheduler/interface/ICompositor.h>
 #include <ui/FenceTime.h>
 #include <ui/Transform.h>
 
@@ -32,12 +34,6 @@
 using Layers = std::vector<sp<compositionengine::LayerFE>>;
 using Outputs = std::vector<std::shared_ptr<compositionengine::Output>>;
 
-struct BorderRenderInfo {
-    float width = 0;
-    half4 color;
-    std::vector<int32_t> layerIds;
-};
-
 // Interface of composition engine power hint callback.
 struct ICEPowerCallback {
     virtual void notifyCpuLoadUp() = 0;
@@ -89,24 +85,22 @@
     // If set, causes the dirty regions to flash with the delay
     std::optional<std::chrono::microseconds> devOptFlashDirtyRegionsDelay;
 
-    // Optional.
-    // The earliest time to send the present command to the HAL.
-    std::optional<std::chrono::steady_clock::time_point> earliestPresentTime;
-
-    // The expected time for the next present
-    nsecs_t expectedPresentTime{0};
+    scheduler::FrameTargets frameTargets;
 
     // The frameInterval for the next present
-    Fps frameInterval{};
+    // TODO (b/315371484): Calculate per display and store on `FrameTarget`.
+    Fps frameInterval;
 
     // If set, a frame has been scheduled for that time.
+    // TODO (b/255601557): Calculate per display.
     std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
 
-    std::vector<BorderRenderInfo> borderInfoList;
-
     bool hasTrustedPresentationListener = false;
 
     ICEPowerCallback* powerCallback = nullptr;
+
+    // System time for when frame refresh starts. Used for stats.
+    nsecs_t refreshStartTime = 0;
 };
 
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index 5e84be1..39748b8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -40,6 +40,9 @@
     // True if the display is secure
     virtual bool isSecure() const = 0;
 
+    // Sets the secure flag for the display
+    virtual void setSecure(bool secure) = 0;
+
     // True if the display is virtual
     virtual bool isVirtual() const = 0;
 
@@ -57,7 +60,7 @@
     virtual void createClientCompositionCache(uint32_t cacheSize) = 0;
 
     // Sends the brightness setting to HWC
-    virtual void applyDisplayBrightness(const bool applyImmediately) = 0;
+    virtual void applyDisplayBrightness(bool applyImmediately) = 0;
 
 protected:
     ~Display() = default;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index a1d6132..4e080b3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -58,8 +58,7 @@
     // Called before composition starts. Should return true if this layer has
     // pending updates which would require an extra display refresh cycle to
     // process.
-    virtual bool onPreComposition(nsecs_t refreshStartTime,
-                                  bool updatingOutputGeometryThisFrame) = 0;
+    virtual bool onPreComposition(bool updatingOutputGeometryThisFrame) = 0;
 
     struct ClientCompositionTargetSettings {
         enum class BlurSetting {
@@ -134,6 +133,15 @@
         uint64_t frameNumber = 0;
     };
 
+    // Describes the states of the release fence. Checking the states allows checks
+    // to ensure that set_value() is not called on the same promise multiple times,
+    // and can indicate if the promise has been fulfilled.
+    enum class ReleaseFencePromiseStatus {
+        UNINITIALIZED, // Promise not created
+        INITIALIZED,   // Promise created, fence has not been set
+        FULFILLED      // Promise fulfilled, fence is set
+    };
+
     // Returns the LayerSettings to pass to RenderEngine::drawLayers. The state may contain shadows
     // casted by the layer or the content of the layer itself. If the layer does not render then an
     // empty optional will be returned.
@@ -143,6 +151,19 @@
     // Called after the layer is displayed to update the presentation fence
     virtual void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack) = 0;
 
+    // Initializes a promise for a buffer release fence and provides the future for that
+    // fence. This should only be called when a promise has not yet been created, or
+    // after the previous promise has already been fulfilled. Attempting to call this
+    // when an existing promise is INITIALIZED will fail because the promise has not
+    // yet been fulfilled.
+    virtual ftl::Future<FenceResult> createReleaseFenceFuture() = 0;
+
+    // Sets promise with its buffer's release fence
+    virtual void setReleaseFence(const FenceResult& releaseFence) = 0;
+
+    // Checks if the buffer's release fence has been set
+    virtual LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() = 0;
+
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index f1d6f52..d420838 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -321,8 +321,11 @@
             const Region& flashRegion,
             std::vector<LayerFE::LayerSettings>& clientCompositionLayers) = 0;
     virtual void setExpensiveRenderingExpected(bool enabled) = 0;
+    virtual void setHintSessionGpuStart(TimePoint startTime) = 0;
     virtual void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) = 0;
+    virtual void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) = 0;
     virtual bool isPowerHintSessionEnabled() = 0;
+    virtual bool isPowerHintSessionGpuReportingEnabled() = 0;
     virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
     virtual bool canPredictCompositionStrategy(const CompositionRefreshArgs&) = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
index c699557..45208dd 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
@@ -48,6 +48,8 @@
 
     void preComposition(CompositionRefreshArgs&) override;
 
+    void postComposition(CompositionRefreshArgs&) override;
+
     FeatureFlags getFeatureFlags() const override;
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index eac5d97..d87968f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -73,7 +73,8 @@
             const compositionengine::DisplayColorProfileCreationArgs&) override;
     void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override;
     void createClientCompositionCache(uint32_t cacheSize) override;
-    void applyDisplayBrightness(const bool applyImmediately) override;
+    void applyDisplayBrightness(bool applyImmediately) override;
+    void setSecure(bool secure) override;
 
     // Internal helpers used by chooseCompositionStrategy()
     using ChangedTypes = android::HWComposer::DeviceRequestedChanges::ChangedTypes;
@@ -92,7 +93,10 @@
 
 private:
     bool isPowerHintSessionEnabled() override;
+    bool isPowerHintSessionGpuReportingEnabled() override;
+    void setHintSessionGpuStart(TimePoint startTime) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+    void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
     DisplayId mId;
     bool mIsDisconnected = false;
     Hwc2::PowerAdvisor* mPowerAdvisor = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 911d67b..adcbbb9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -144,8 +144,11 @@
             std::vector<LayerFE*>& outLayerFEs) override;
     void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
+    void setHintSessionGpuStart(TimePoint startTime) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+    void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
     bool isPowerHintSessionEnabled() override;
+    bool isPowerHintSessionGpuReportingEnabled() override;
     void dumpBase(std::string&) const;
 
     // Implemented by the final implementation for the final state it uses.
@@ -162,7 +165,6 @@
 
 private:
     void dirtyEntireOutput();
-    void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&);
     compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const;
     void finishPrepareFrame();
     ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 6b1c318..f8ffde1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -34,7 +34,6 @@
 
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/ProjectionSpace.h>
-#include <renderengine/BorderRenderInfo.h>
 #include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
@@ -166,8 +165,6 @@
 
     bool treat170mAsSrgb = false;
 
-    std::vector<renderengine::BorderRenderInfo> borderInfoList;
-
     uint64_t lastOutputLayerHash = 0;
     uint64_t outputLayerHash = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index d26ca9d..86bcf20 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -143,12 +143,10 @@
 
     compositionengine::OutputLayer* getBlurLayer() const;
 
-    bool hasUnsupportedDataspace() const;
+    bool hasKnownColorShift() const;
 
     bool hasProtectedLayers() const;
 
-    bool hasSolidColorLayers() const;
-
     // True if any layer in this cached set has CachingHint::Disabled
     bool cachingHintExcludesLayers() const;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index 1f241b0..5e3e3d8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -74,6 +74,7 @@
     BlurRegions           = 1u << 18,
     HasProtectedContent   = 1u << 19,
     CachingHint           = 1u << 20,
+    DimmingEnabled        = 1u << 21,
 };
 // clang-format on
 
@@ -248,6 +249,10 @@
 
     ui::Dataspace getDataspace() const { return mOutputDataspace.get(); }
 
+    hardware::graphics::composer::hal::PixelFormat getPixelFormat() const {
+        return mPixelFormat.get();
+    }
+
     float getHdrSdrRatio() const {
         return getOutputLayer()->getLayerFE().getCompositionState()->currentHdrSdrRatio;
     };
@@ -258,10 +263,7 @@
 
     gui::CachingHint getCachingHint() const { return mCachingHint.get(); }
 
-    bool hasSolidColorCompositionType() const {
-        return getOutputLayer()->getLayerFE().getCompositionState()->compositionType ==
-                aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR;
-    }
+    bool isDimmingEnabled() const { return mIsDimmingEnabled.get(); }
 
     float getFps() const { return getOutputLayer()->getLayerFE().getCompositionState()->fps; }
 
@@ -503,7 +505,10 @@
                              return std::vector<std::string>{toString(cachingHint)};
                          }};
 
-    static const constexpr size_t kNumNonUniqueFields = 19;
+    OutputLayerState<bool, LayerStateField::DimmingEnabled> mIsDimmingEnabled{
+            [](auto layer) { return layer->getLayerFE().getCompositionState()->dimmingEnabled; }};
+
+    static const constexpr size_t kNumNonUniqueFields = 20;
 
     std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
         std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -521,7 +526,7 @@
                 &mAlpha,        &mLayerMetadata,  &mVisibleRegion,        &mOutputDataspace,
                 &mPixelFormat,  &mColorTransform, &mCompositionType,      &mSidebandStream,
                 &mBuffer,       &mSolidColor,     &mBackgroundBlurRadius, &mBlurRegions,
-                &mFrameNumber,  &mIsProtected,    &mCachingHint};
+                &mFrameNumber,  &mIsProtected,    &mCachingHint,          &mIsDimmingEnabled};
     }
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
index 9b2387b..a1b7282 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
@@ -52,6 +52,7 @@
     MOCK_METHOD1(updateCursorAsync, void(CompositionRefreshArgs&));
 
     MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&));
+    MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&));
 
     MOCK_CONST_METHOD0(getFeatureFlags, FeatureFlags());
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index 7e99ec2..46cb95e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -33,6 +33,7 @@
 
     MOCK_CONST_METHOD0(getId, DisplayId());
     MOCK_CONST_METHOD0(isSecure, bool());
+    MOCK_METHOD1(setSecure, void(bool));
     MOCK_CONST_METHOD0(isVirtual, bool());
     MOCK_CONST_METHOD0(getPreferredBootHwcConfigId, int32_t());
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 15e4577..05a5d38 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -20,6 +20,7 @@
 #include <compositionengine/LayerFECompositionState.h>
 #include <gmock/gmock.h>
 #include <ui/Fence.h>
+#include "ui/FenceResult.h"
 
 namespace android::compositionengine::mock {
 
@@ -43,7 +44,7 @@
 
     MOCK_CONST_METHOD0(getCompositionState, const LayerFECompositionState*());
 
-    MOCK_METHOD2(onPreComposition, bool(nsecs_t, bool));
+    MOCK_METHOD1(onPreComposition, bool(bool));
 
     MOCK_CONST_METHOD1(prepareClientComposition,
                        std::optional<compositionengine::LayerFE::LayerSettings>(
@@ -52,6 +53,9 @@
     MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture<FenceResult>, ui::LayerStack),
                 (override));
 
+    MOCK_METHOD0(createReleaseFenceFuture, ftl::Future<FenceResult>());
+    MOCK_METHOD1(setReleaseFence, void(const FenceResult&));
+    MOCK_METHOD0(getReleaseFencePromiseStatus, LayerFE::ReleaseFencePromiseStatus());
     MOCK_CONST_METHOD0(getDebugName, const char*());
     MOCK_CONST_METHOD0(getSequence, int32_t());
     MOCK_CONST_METHOD0(hasRoundedCorners, bool());
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 95ea3a4..3f3deae 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -134,8 +134,11 @@
     MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
     MOCK_METHOD1(setPredictCompositionStrategy, void(bool));
     MOCK_METHOD1(setTreat170mAsSrgb, void(bool));
+    MOCK_METHOD(void, setHintSessionGpuStart, (TimePoint startTime));
     MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence));
+    MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine));
     MOCK_METHOD(bool, isPowerHintSessionEnabled, ());
+    MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, ());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index d87eae3..4c77687 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -162,6 +162,7 @@
             future.get();
         }
     }
+    postComposition(args);
 }
 
 void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) {
@@ -181,10 +182,10 @@
 
     bool needsAnotherUpdate = false;
 
-    mRefreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mRefreshStartTime = args.refreshStartTime;
 
     for (auto& layer : args.layers) {
-        if (layer->onPreComposition(mRefreshStartTime, args.updatingOutputGeometryThisFrame)) {
+        if (layer->onPreComposition(args.updatingOutputGeometryThisFrame)) {
             needsAnotherUpdate = true;
         }
     }
@@ -192,6 +193,34 @@
     mNeedsAnotherUpdate = needsAnotherUpdate;
 }
 
+// If a buffer is latched but the layer is not presented, such as when
+// obscured by another layer, the previous buffer needs to be released. We find
+// these buffers and fire a NO_FENCE to release it. This ensures that all
+// promises for buffer releases are fulfilled at the end of composition.
+void CompositionEngine::postComposition(CompositionRefreshArgs& args) {
+    if (FlagManager::getInstance().ce_fence_promise()) {
+        ATRACE_CALL();
+        ALOGV(__FUNCTION__);
+
+        for (auto& layerFE : args.layers) {
+            if (layerFE->getReleaseFencePromiseStatus() ==
+                LayerFE::ReleaseFencePromiseStatus::INITIALIZED) {
+                layerFE->setReleaseFence(Fence::NO_FENCE);
+            }
+        }
+
+        // List of layersWithQueuedFrames does not necessarily overlap with
+        // list of layers, so those layersWithQueuedFrames also need any
+        // unfulfilled promises to be resolved for completeness.
+        for (auto& layerFE : args.layersWithQueuedFrames) {
+            if (layerFE->getReleaseFencePromiseStatus() ==
+                LayerFE::ReleaseFencePromiseStatus::INITIALIZED) {
+                layerFE->setReleaseFence(Fence::NO_FENCE);
+            }
+        }
+    }
+}
+
 FeatureFlags CompositionEngine::getFeatureFlags() const {
     return {};
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 7a727fd..c18be7a 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -74,6 +74,10 @@
     return getState().isSecure;
 }
 
+void Display::setSecure(bool secure) {
+    editState().isSecure = secure;
+}
+
 bool Display::isVirtual() const {
     return VirtualDisplayId::tryCast(mId).has_value();
 }
@@ -200,13 +204,12 @@
     setReleasedLayers(std::move(releasedLayers));
 }
 
-void Display::applyDisplayBrightness(const bool applyImmediately) {
-    auto& hwc = getCompositionEngine().getHwComposer();
-    const auto halDisplayId = HalDisplayId::tryCast(*getDisplayId());
-    if (const auto physicalDisplayId = PhysicalDisplayId::tryCast(*halDisplayId);
-        physicalDisplayId && getState().displayBrightness) {
+void Display::applyDisplayBrightness(bool applyImmediately) {
+    if (const auto displayId = ftl::Optional(getDisplayId()).and_then(PhysicalDisplayId::tryCast);
+        displayId && getState().displayBrightness) {
+        auto& hwc = getCompositionEngine().getHwComposer();
         const status_t result =
-                hwc.setDisplayBrightness(*physicalDisplayId, *getState().displayBrightness,
+                hwc.setDisplayBrightness(*displayId, *getState().displayBrightness,
                                          getState().displayBrightnessNits,
                                          Hwc2::Composer::DisplayBrightnessOptions{
                                                  .applyImmediately = applyImmediately})
@@ -249,10 +252,6 @@
     auto& hwc = getCompositionEngine().getHwComposer();
     const bool requiresClientComposition = anyLayersRequireClientComposition();
 
-    if (isPowerHintSessionEnabled()) {
-        mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition);
-    }
-
     const TimePoint hwcValidateStartTime = TimePoint::now();
 
     if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
@@ -413,10 +412,24 @@
     return mPowerAdvisor != nullptr && mPowerAdvisor->usePowerHintSession();
 }
 
+bool Display::isPowerHintSessionGpuReportingEnabled() {
+    return mPowerAdvisor != nullptr && mPowerAdvisor->supportsGpuReporting();
+}
+
+// For ADPF GPU v0 this is expected to set start time to when the GPU commands are submitted with
+// fence returned, i.e. when RenderEngine flushes the commands and returns the draw fence.
+void Display::setHintSessionGpuStart(TimePoint startTime) {
+    mPowerAdvisor->setGpuStartTime(mId, startTime);
+}
+
 void Display::setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) {
     mPowerAdvisor->setGpuFenceTime(mId, std::move(gpuFence));
 }
 
+void Display::setHintSessionRequiresRenderEngine(bool requiresRenderEngine) {
+    mPowerAdvisor->setRequiresRenderEngine(mId, requiresRenderEngine);
+}
+
 void Display::finishFrame(GpuCompositionResult&& result) {
     // We only need to actually compose the display if:
     // 1) It is being handled by hardware composer, which may need this to
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 55db5b6..84f3f25 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -16,6 +16,7 @@
 
 #include <SurfaceFlingerProperties.sysprop.h>
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -28,8 +29,11 @@
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/impl/planner/Planner.h>
+#include <ftl/algorithm.h>
 #include <ftl/future.h>
 #include <gui/TraceUtils.h>
+#include <scheduler/FrameTargeter.h>
+#include <scheduler/Time.h>
 
 #include <optional>
 #include <thread>
@@ -429,7 +433,28 @@
 
 ftl::Future<std::monostate> Output::present(
         const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
+    const auto stringifyExpectedPresentTime = [this, &refreshArgs]() -> std::string {
+        return ftl::Optional(getDisplayId())
+                .and_then(PhysicalDisplayId::tryCast)
+                .and_then([&refreshArgs](PhysicalDisplayId id) {
+                    return refreshArgs.frameTargets.get(id);
+                })
+                .transform([](const auto& frameTargetPtr) {
+                    return frameTargetPtr.get()->expectedPresentTime();
+                })
+                .transform([](TimePoint expectedPresentTime) {
+                    return base::StringPrintf(" vsyncIn %.2fms",
+                                              ticks<std::milli, float>(expectedPresentTime -
+                                                                       TimePoint::now()));
+                })
+                .or_else([] {
+                    // There is no vsync for this output.
+                    return std::make_optional(std::string());
+                })
+                .value();
+    };
+    ATRACE_FORMAT("%s for %s%s", __func__, mNamePlusId.c_str(),
+                  stringifyExpectedPresentTime().c_str());
     ALOGV(__FUNCTION__);
 
     updateColorProfile(refreshArgs);
@@ -439,6 +464,10 @@
     setColorTransform(refreshArgs);
     beginFrame();
 
+    if (isPowerHintSessionEnabled()) {
+        // always reset the flag before the composition prediction
+        setHintSessionRequiresRenderEngine(false);
+    }
     GpuCompositionResult result;
     const bool predictCompositionStrategy = canPredictCompositionStrategy(refreshArgs);
     if (predictCompositionStrategy) {
@@ -794,44 +823,6 @@
             forceClientComposition = false;
         }
     }
-
-    updateCompositionStateForBorder(refreshArgs);
-}
-
-void Output::updateCompositionStateForBorder(
-        const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    std::unordered_map<int32_t, const Region*> layerVisibleRegionMap;
-    // Store a map of layerId to their computed visible region.
-    for (auto* layer : getOutputLayersOrderedByZ()) {
-        int layerId = (layer->getLayerFE()).getSequence();
-        layerVisibleRegionMap[layerId] = &((layer->getState()).visibleRegion);
-    }
-    OutputCompositionState& outputCompositionState = editState();
-    outputCompositionState.borderInfoList.clear();
-    bool clientComposeTopLayer = false;
-    for (const auto& borderInfo : refreshArgs.borderInfoList) {
-        renderengine::BorderRenderInfo info;
-        for (const auto& id : borderInfo.layerIds) {
-            info.combinedRegion.orSelf(*(layerVisibleRegionMap[id]));
-        }
-
-        if (!info.combinedRegion.isEmpty()) {
-            info.width = borderInfo.width;
-            info.color = borderInfo.color;
-            outputCompositionState.borderInfoList.emplace_back(std::move(info));
-            clientComposeTopLayer = true;
-        }
-    }
-
-    // In this situation we must client compose the top layer instead of using hwc
-    // because we want to draw the border above all else.
-    // This could potentially cause a bit of a performance regression if the top
-    // layer would have been rendered using hwc originally.
-    // TODO(b/227656283): Measure system's performance before enabling the border feature
-    if (clientComposeTopLayer) {
-        auto topLayer = getOutputLayerOrderedByZByIndex(getOutputLayerCount() - 1);
-        (topLayer->editState()).forceClientComposition = true;
-    }
 }
 
 void Output::planComposition() {
@@ -853,8 +844,14 @@
         return;
     }
 
-    editState().earliestPresentTime = refreshArgs.earliestPresentTime;
-    editState().expectedPresentTime = refreshArgs.expectedPresentTime;
+    if (auto frameTargetPtrOpt = ftl::Optional(getDisplayId())
+                                         .and_then(PhysicalDisplayId::tryCast)
+                                         .and_then([&refreshArgs](PhysicalDisplayId id) {
+                                             return refreshArgs.frameTargets.get(id);
+                                         })) {
+        editState().earliestPresentTime = frameTargetPtrOpt->get()->earliestPresentTime();
+        editState().expectedPresentTime = frameTargetPtrOpt->get()->expectedPresentTime().ns();
+    }
     editState().frameInterval = refreshArgs.frameInterval;
     editState().powerCallback = refreshArgs.powerCallback;
 
@@ -1217,8 +1214,7 @@
     if (!optReadyFence) {
         return;
     }
-
-    if (isPowerHintSessionEnabled()) {
+    if (isPowerHintSessionEnabled() && !isPowerHintSessionGpuReportingEnabled()) {
         // get fence end time to know when gpu is complete in display
         setHintSessionGpuFence(
                 std::make_unique<FenceTime>(sp<Fence>::make(dup(optReadyFence->get()))));
@@ -1246,8 +1242,9 @@
     if (isProtected && supportsProtectedContent) {
         auto layers = getOutputLayersOrderedByZ();
         bool needsProtected = std::any_of(layers.begin(), layers.end(), [](auto* layer) {
-            return layer->getLayerFE().getCompositionState()->hasProtectedContent
-                    && layer->requiresClientComposition();
+            return layer->getLayerFE().getCompositionState()->hasProtectedContent &&
+                    (!FlagManager::getInstance().protected_if_client() ||
+                     layer->requiresClientComposition());
         });
         if (needsProtected != mRenderSurface->isProtected()) {
             mRenderSurface->setProtected(needsProtected);
@@ -1361,8 +1358,20 @@
         // If rendering was not successful, remove the request from the cache.
         mClientCompositionRequestCache->remove(tex->getBuffer()->getId());
     }
-
     const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE);
+    if (isPowerHintSessionEnabled()) {
+        if (fence != Fence::NO_FENCE && fence->isValid() &&
+            !outputCompositionState.reusedClientComposition) {
+            setHintSessionRequiresRenderEngine(true);
+            if (isPowerHintSessionGpuReportingEnabled()) {
+                // the order of the two calls here matters as we should check if the previously
+                // tracked fence has signaled first and archive the previous start time
+                setHintSessionGpuStart(TimePoint::now());
+                setHintSessionGpuFence(
+                        std::make_unique<FenceTime>(sp<Fence>::make(dup(fence->get()))));
+            }
+        }
+    }
 
     if (auto timeStats = getCompositionEngine().getTimeStats()) {
         if (fence->isValid()) {
@@ -1412,13 +1421,6 @@
 
     // Compute the global color transform matrix.
     clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
-    for (auto& info : outputState.borderInfoList) {
-        renderengine::BorderRenderInfo borderInfo;
-        borderInfo.width = info.width;
-        borderInfo.color = info.color;
-        borderInfo.combinedRegion = info.combinedRegion;
-        clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
-    }
     clientCompositionDisplay.deviceHandlesColorTransform =
             outputState.usesDeviceComposition || getSkipColorTransform();
     return clientCompositionDisplay;
@@ -1545,14 +1547,26 @@
     // The base class does nothing with this call.
 }
 
+void Output::setHintSessionGpuStart(TimePoint) {
+    // The base class does nothing with this call.
+}
+
 void Output::setHintSessionGpuFence(std::unique_ptr<FenceTime>&&) {
     // The base class does nothing with this call.
 }
 
+void Output::setHintSessionRequiresRenderEngine(bool) {
+    // The base class does nothing with this call.
+}
+
 bool Output::isPowerHintSessionEnabled() {
     return false;
 }
 
+bool Output::isPowerHintSessionGpuReportingEnabled() {
+    return false;
+}
+
 void Output::presentFrameAndReleaseLayers() {
     ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
@@ -1590,9 +1604,13 @@
             releaseFence =
                     Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
         }
-        layer->getLayerFE()
-                .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
-                                  outputState.layerFilter.layerStack);
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            layer->getLayerFE().setReleaseFence(releaseFence);
+        } else {
+            layer->getLayerFE()
+                    .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
+                                      outputState.layerFilter.layerStack);
+        }
     }
 
     // We've got a list of layers needing fences, that are disjoint with
@@ -1600,8 +1618,12 @@
     // supply them with the present fence.
     for (auto& weakLayer : mReleasedLayers) {
         if (const auto layer = weakLayer.promote()) {
-            layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
-                                    outputState.layerFilter.layerStack);
+            if (FlagManager::getInstance().ce_fence_promise()) {
+                layer->setReleaseFence(frame.presentFence);
+            } else {
+                layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
+                                        outputState.layerFilter.layerStack);
+            }
         }
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 39cf671..6683e67 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -67,11 +67,6 @@
 
     out.append("\n   ");
     dumpVal(out, "treat170mAsSrgb", treat170mAsSrgb);
-
-    out.append("\n");
-    for (const auto& borderRenderInfo : borderInfoList) {
-        dumpVal(out, "borderRegion", borderRenderInfo.combinedRegion);
-    }
     out.append("\n");
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 7fe3369..091c207 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -394,7 +394,6 @@
     auto requestedCompositionType = outputIndependentState->compositionType;
 
     if (requestedCompositionType == Composition::SOLID_COLOR && state.overrideInfo.buffer) {
-        // this should never happen, as SOLID_COLOR is skipped from caching, b/230073351
         requestedCompositionType = Composition::DEVICE;
     }
 
@@ -665,6 +664,9 @@
 void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer,
                                         const LayerFECompositionState& outputIndependentState,
                                         bool skipLayer) {
+    if (skipLayer && outputIndependentState.buffer == nullptr) {
+        return;
+    }
     auto supportedPerFrameMetadata =
             getOutput().getDisplayColorProfile()->getSupportedPerFrameMetadata();
     if (auto error = hwcLayer->setPerFrameMetadata(supportedPerFrameMetadata,
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 869dda6..ea9442d 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -27,8 +27,7 @@
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/RenderEngine.h>
 #include <ui/DebugUtils.h>
-#include <utils/Trace.h>
-
+#include <ui/HdrRenderTypeUtils.h>
 #include <utils/Trace.h>
 
 namespace android::compositionengine::impl::planner {
@@ -306,7 +305,7 @@
         return false;
     }
 
-    if (hasUnsupportedDataspace()) {
+    if (hasKnownColorShift()) {
         return false;
     }
 
@@ -366,12 +365,21 @@
     return mBlurLayer ? mBlurLayer->getOutputLayer() : nullptr;
 }
 
-bool CachedSet::hasUnsupportedDataspace() const {
+bool CachedSet::hasKnownColorShift() const {
     return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) {
         auto dataspace = layer.getState()->getDataspace();
-        const auto transfer = static_cast<ui::Dataspace>(dataspace & ui::Dataspace::TRANSFER_MASK);
-        if (transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG) {
-            // Skip HDR.
+
+        // Layers are never dimmed when rendering a cached set, meaning that we may ask HWC to
+        // dim a cached set. But this means that we can never cache any HDR layers so that we
+        // don't accidentally dim those layers.
+        const auto hdrType = getHdrRenderType(dataspace, layer.getState()->getPixelFormat(),
+                                              layer.getState()->getHdrSdrRatio());
+        if (hdrType != HdrRenderType::SDR) {
+            return true;
+        }
+
+        // Layers that have dimming disabled pretend that they're HDR.
+        if (!layer.getState()->isDimmingEnabled()) {
             return true;
         }
 
@@ -380,10 +388,6 @@
             // to avoid flickering/color differences.
             return true;
         }
-        // TODO(b/274804887): temp fix of overdimming issue, skip caching if hsdr/sdr ratio > 1.01f
-        if (layer.getState()->getHdrSdrRatio() > 1.01f) {
-            return true;
-        }
         return false;
     });
 }
@@ -393,12 +397,6 @@
                        [](const Layer& layer) { return layer.getState()->isProtected(); });
 }
 
-bool CachedSet::hasSolidColorLayers() const {
-    return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) {
-        return layer.getState()->hasSolidColorCompositionType();
-    });
-}
-
 bool CachedSet::cachingHintExcludesLayers() const {
     const bool shouldExcludeLayers =
             std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index a18397d..4bafed2 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -56,7 +56,7 @@
         }
 
         // Do not unflatten if source crop is only moved.
-        if (FlagManager::getInstance().cache_if_source_crop_layer_only_moved() &&
+        if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() &&
             incomingLayers[i]->isSourceCropSizeEqual(*(existingLayers[i])) &&
             incomingLayers[i]->getDifferingFields(*(existingLayers[i])) ==
                     LayerStateField::SourceCrop) {
@@ -439,7 +439,7 @@
 
         if (!layerDeniedFromCaching && layerIsInactive &&
             (firstLayer || runHasFirstLayer || !layerHasBlur) &&
-            !currentSet->hasUnsupportedDataspace()) {
+            !currentSet->hasKnownColorShift()) {
             if (isPartOfRun) {
                 builder.increment();
             } else {
@@ -513,13 +513,6 @@
         }
     }
 
-    for (const CachedSet& layer : mLayers) {
-        if (layer.hasSolidColorLayers()) {
-            ATRACE_NAME("layer->hasSolidColorLayers()");
-            return;
-        }
-    }
-
     std::vector<Run> runs = findCandidateRuns(now);
 
     std::optional<Run> bestRun = findBestRun(runs);
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index 0e3fdbb..10dc927 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <common/FlagManager.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
 namespace {
@@ -70,6 +71,10 @@
         if (field->getField() == LayerStateField::Buffer) {
             continue;
         }
+        if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() &&
+            field->getField() == LayerStateField::SourceCrop) {
+            continue;
+        }
         android::hashCombineSingleHashed(hash, field->getHash());
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index da578e2..639164d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -28,6 +28,7 @@
 
 #include "MockHWComposer.h"
 #include "TimeStats/TimeStats.h"
+#include "gmock/gmock.h"
 
 #include <variant>
 
@@ -90,14 +91,16 @@
         // These are the overridable functions CompositionEngine::present() may
         // call, and have separate test coverage.
         MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&));
+        MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&));
     };
 
     StrictMock<CompositionEnginePartialMock> mEngine;
 };
 
 TEST_F(CompositionEnginePresentTest, worksWithEmptyRequest) {
-    // present() always calls preComposition()
+    // present() always calls preComposition() and postComposition()
     EXPECT_CALL(mEngine, preComposition(Ref(mRefreshArgs)));
+    EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs)));
 
     mEngine.present(mRefreshArgs);
 }
@@ -126,6 +129,9 @@
     EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs)))
             .WillOnce(Return(ftl::yield<std::monostate>({})));
 
+    // present() always calls postComposition()
+    EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs)));
+
     mRefreshArgs.outputs = {mOutput1, mOutput2, mOutput3};
     mEngine.present(mRefreshArgs);
 }
@@ -214,6 +220,7 @@
 
 TEST_F(CompositionTestPreComposition, preCompositionSetsFrameTimestamp) {
     const nsecs_t before = systemTime(SYSTEM_TIME_MONOTONIC);
+    mRefreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
     mEngine.preComposition(mRefreshArgs);
     const nsecs_t after = systemTime(SYSTEM_TIME_MONOTONIC);
 
@@ -226,12 +233,9 @@
     nsecs_t ts1 = 0;
     nsecs_t ts2 = 0;
     nsecs_t ts3 = 0;
-    EXPECT_CALL(*mLayer1FE, onPreComposition(_, _))
-            .WillOnce(DoAll(SaveArg<0>(&ts1), Return(false)));
-    EXPECT_CALL(*mLayer2FE, onPreComposition(_, _))
-            .WillOnce(DoAll(SaveArg<0>(&ts2), Return(false)));
-    EXPECT_CALL(*mLayer3FE, onPreComposition(_, _))
-            .WillOnce(DoAll(SaveArg<0>(&ts3), Return(false)));
+    EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts1), Return(false)));
+    EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts2), Return(false)));
+    EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts3), Return(false)));
 
     mRefreshArgs.outputs = {mOutput1};
     mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
@@ -245,9 +249,9 @@
 }
 
 TEST_F(CompositionTestPreComposition, preCompositionDefaultsToNoUpdateNeeded) {
-    EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(false));
-    EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false));
-    EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false));
 
     mEngine.setNeedsAnotherUpdateForTest(true);
 
@@ -262,9 +266,9 @@
 
 TEST_F(CompositionTestPreComposition,
        preCompositionSetsNeedsAnotherUpdateIfAtLeastOneLayerRequestsIt) {
-    EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(true));
-    EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false));
-    EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(true));
+    EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false));
 
     mRefreshArgs.outputs = {mOutput1};
     mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
@@ -483,5 +487,29 @@
     mEngine.present(mRefreshArgs);
 }
 
+struct CompositionEnginePostCompositionTest : public CompositionEngineTest {
+    sp<StrictMock<mock::LayerFE>> mLayer1FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> mLayer2FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> mLayer3FE = sp<StrictMock<mock::LayerFE>>::make();
+};
+
+TEST_F(CompositionEnginePostCompositionTest, postCompositionReleasesAllFences) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    EXPECT_CALL(*mLayer1FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED));
+    EXPECT_CALL(*mLayer2FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED));
+    EXPECT_CALL(*mLayer3FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::INITIALIZED));
+    mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
+
+    EXPECT_CALL(*mLayer1FE, setReleaseFence(_)).Times(0);
+    EXPECT_CALL(*mLayer2FE, setReleaseFence(_)).Times(0);
+    EXPECT_CALL(*mLayer3FE, setReleaseFence(_)).Times(1);
+
+    mEngine.postComposition(mRefreshArgs);
+}
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 9e35717..4612117 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -97,13 +97,13 @@
     MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId));
     MOCK_CONST_METHOD2(getModes,
                        std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId, int32_t));
-    MOCK_CONST_METHOD1(getActiveMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
+    MOCK_CONST_METHOD1(getActiveMode, ftl::Expected<hal::HWConfigId, status_t>(PhysicalDisplayId));
     MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(PhysicalDisplayId));
     MOCK_METHOD3(setActiveColorMode, status_t(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent));
     MOCK_CONST_METHOD0(isUsingVrComposer, bool());
     MOCK_CONST_METHOD1(getDisplayConnectionType, ui::DisplayConnectionType(PhysicalDisplayId));
     MOCK_CONST_METHOD1(isVsyncPeriodSwitchSupported, bool(PhysicalDisplayId));
-    MOCK_CONST_METHOD2(getDisplayVsyncPeriod, status_t(PhysicalDisplayId, nsecs_t*));
+    MOCK_CONST_METHOD1(getDisplayVsyncPeriod, ftl::Expected<nsecs_t, status_t>(PhysicalDisplayId));
     MOCK_METHOD4(setActiveModeWithConstraints,
                  status_t(PhysicalDisplayId, hal::HWConfigId,
                           const hal::VsyncPeriodChangeConstraints&,
@@ -126,6 +126,7 @@
                        const std::unordered_map<std::string, bool>&());
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_CONST_METHOD1(dumpOverlayProperties, void(std::string&));
     MOCK_CONST_METHOD0(getComposer, android::Hwc2::Composer*());
 
     MOCK_METHOD(hal::HWDisplayId, getPrimaryHwcDisplayId, (), (const, override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index 7253354..ed2ffa9 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -38,10 +38,12 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, supportsGpuReporting, (), (override));
     MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
+    MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
     MOCK_METHOD(void, setHwcValidateTiming,
@@ -51,8 +53,8 @@
                 (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
                 (override));
     MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
-    MOCK_METHOD(void, setRequiresClientComposition,
-                (DisplayId displayId, bool requiresClientComposition), (override));
+    MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
+                (override));
     MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
     MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
                 (override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 97a2de0..0dc3c9f 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -2017,8 +2017,17 @@
         MOCK_METHOD0(presentFrameAndReleaseLayers, void());
         MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
+        MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
+    OutputPresentTest() {
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
+    }
+
     StrictMock<OutputPartialMock> mOutput;
 };
 
@@ -2032,6 +2041,7 @@
     EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(false));
     EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(false));
     EXPECT_CALL(mOutput, prepareFrame());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
@@ -2052,6 +2062,7 @@
     EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(false));
     EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(true));
     EXPECT_CALL(mOutput, prepareFrameAsync());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
@@ -2989,6 +3000,10 @@
         MOCK_METHOD0(updateProtectedContentState, void());
         MOCK_METHOD2(dequeueRenderBuffer,
                      bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
+        MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
     OutputFinishFrameTest() {
@@ -2997,6 +3012,8 @@
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
         EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
     }
 
     StrictMock<OutputPartialMock> mOutput;
@@ -3023,6 +3040,22 @@
     mOutput.finishFrame(std::move(result));
 }
 
+TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFenceWithAdpfGpuOff) {
+    EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillOnce(Return(false));
+    mOutput.mState.isEnabled = true;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
+            .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
+
+    impl::GpuCompositionResult result;
+    mOutput.finishFrame(std::move(result));
+}
+
 TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) {
     mOutput.mState.isEnabled = true;
 
@@ -3031,6 +3064,7 @@
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
 
     impl::GpuCompositionResult result;
@@ -3058,6 +3092,7 @@
             .WillOnce(DoAll(SetArgPointee<1>(texture), Return(true)));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 2.f));
 
     impl::GpuCompositionResult result;
@@ -3068,6 +3103,7 @@
     mOutput.mState.isEnabled = true;
     mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::SUCCESS;
     InSequence seq;
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
 
     impl::GpuCompositionResult result;
@@ -3090,6 +3126,7 @@
                 composeSurfaces(RegionEq(Region::INVALID_REGION), result.buffer,
                                 Eq(ByRef(result.fence))))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
     mOutput.finishFrame(std::move(result));
 }
@@ -3164,6 +3201,8 @@
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
     // Simulate getting release fences from each layer, and ensure they are passed to the
     // front-end layer interface for each layer correctly.
 
@@ -3205,7 +3244,51 @@
     mOutput.presentFrameAndReleaseLayers();
 }
 
+TEST_F(OutputPostFramebufferTest, releaseFencesAreSetInLayerFE) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+    // Simulate getting release fences from each layer, and ensure they are passed to the
+    // front-end layer interface for each layer correctly.
+
+    mOutput.mState.isEnabled = true;
+
+    // Create three unique fence instances
+    sp<Fence> layer1Fence = sp<Fence>::make();
+    sp<Fence> layer2Fence = sp<Fence>::make();
+    sp<Fence> layer3Fence = sp<Fence>::make();
+
+    Output::FrameFences frameFences;
+    frameFences.layerFences.emplace(&mLayer1.hwc2Layer, layer1Fence);
+    frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence);
+    frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence);
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Compare the pointers values of each fence to make sure the correct ones
+    // are passed. This happens to work with the current implementation, but
+    // would not survive certain calls like Fence::merge() which would return a
+    // new instance.
+    EXPECT_CALL(*mLayer1.layerFE, setReleaseFence(_))
+            .WillOnce([&layer1Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer1Fence), releaseFence);
+            });
+    EXPECT_CALL(*mLayer2.layerFE, setReleaseFence(_))
+            .WillOnce([&layer2Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer2Fence), releaseFence);
+            });
+    EXPECT_CALL(*mLayer3.layerFE, setReleaseFence(_))
+            .WillOnce([&layer3Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer3Fence), releaseFence);
+            });
+
+    mOutput.presentFrameAndReleaseLayers();
+}
+
 TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
+
     mOutput.mState.isEnabled = true;
     mOutput.mState.usesClientComposition = true;
 
@@ -3228,7 +3311,35 @@
     mOutput.presentFrameAndReleaseLayers();
 }
 
+TEST_F(OutputPostFramebufferTest, setReleaseFencesIncludeClientTargetAcquireFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.usesClientComposition = true;
+
+    Output::FrameFences frameFences;
+    frameFences.clientTargetAcquireFence = sp<Fence>::make();
+    frameFences.layerFences.emplace(&mLayer1.hwc2Layer, sp<Fence>::make());
+    frameFences.layerFences.emplace(&mLayer2.hwc2Layer, sp<Fence>::make());
+    frameFences.layerFences.emplace(&mLayer3.hwc2Layer, sp<Fence>::make());
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Fence::merge is called, and since none of the fences are actually valid,
+    // Fence::NO_FENCE is returned and passed to each setReleaseFence() call.
+    // This is the best we can do without creating a real kernel fence object.
+    EXPECT_CALL(*mLayer1.layerFE, setReleaseFence).WillOnce(Return());
+    EXPECT_CALL(*mLayer2.layerFE, setReleaseFence).WillOnce(Return());
+    EXPECT_CALL(*mLayer3.layerFE, setReleaseFence).WillOnce(Return());
+    mOutput.presentFrameAndReleaseLayers();
+}
+
 TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
+
     mOutput.mState.isEnabled = true;
     mOutput.mState.usesClientComposition = true;
 
@@ -3276,6 +3387,54 @@
     EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
 }
 
+TEST_F(OutputPostFramebufferTest, setReleasedLayersSentPresentFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.usesClientComposition = true;
+
+    // This should happen even if there are no (current) output layers.
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // Load up the released layers with some mock instances
+    sp<StrictMock<mock::LayerFE>> releasedLayer1 = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> releasedLayer2 = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> releasedLayer3 = sp<StrictMock<mock::LayerFE>>::make();
+    Output::ReleasedLayers layers;
+    layers.push_back(releasedLayer1);
+    layers.push_back(releasedLayer2);
+    layers.push_back(releasedLayer3);
+    mOutput.setReleasedLayers(std::move(layers));
+
+    // Set up a fake present fence
+    sp<Fence> presentFence = sp<Fence>::make();
+    Output::FrameFences frameFences;
+    frameFences.presentFence = presentFence;
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Each released layer should be given the presentFence.
+    EXPECT_CALL(*releasedLayer1, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+    EXPECT_CALL(*releasedLayer2, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+    EXPECT_CALL(*releasedLayer3, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+
+    mOutput.presentFrameAndReleaseLayers();
+
+    // After the call the list of released layers should have been cleared.
+    EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
+}
+
 /*
  * Output::composeSurfaces()
  */
@@ -3293,9 +3452,12 @@
         MOCK_METHOD2(appendRegionFlashRequests,
                      void(const Region&, std::vector<LayerFE::LayerSettings>&));
         MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
+        MOCK_METHOD(void, setHintSessionGpuStart, (TimePoint startTime), (override));
         MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
                     (override));
+        MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool), (override));
         MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
     OutputComposeSurfacesTest() {
@@ -3325,6 +3487,8 @@
         EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get()));
         EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities())
                 .WillRepeatedly(ReturnRef(kHdrCapabilities));
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
     }
 
     struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
@@ -3590,10 +3754,57 @@
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
 
     // We do not expect another call to draw layers.
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     verify().execute().expectAFenceWasReturned();
     EXPECT_TRUE(mOutput.mState.reusedClientComposition);
 }
 
+TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChangesWithAdpfGpuOff) {
+    EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillOnce(Return(false));
+    LayerFE::LayerSettings r1;
+    LayerFE::LayerSettings r2;
+
+    r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+    r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
+
+    const auto otherOutputBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
+            .WillOnce(Return(mOutputBuffer))
+            .WillOnce(Return(otherOutputBuffer));
+    base::unique_fd fd(open("/dev/null", O_RDONLY));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
+            .WillRepeatedly([&](const renderengine::DisplaySettings&,
+                                const std::vector<renderengine::LayerSettings>&,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
+                                base::unique_fd&&) -> ftl::Future<FenceResult> {
+                return ftl::yield<FenceResult>(sp<Fence>::make(std::move(fd)));
+            });
+
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(true));
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+}
+
 TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) {
     LayerFE::LayerSettings r1;
     LayerFE::LayerSettings r2;
@@ -3618,14 +3829,18 @@
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
             .WillOnce(Return(mOutputBuffer))
             .WillOnce(Return(otherOutputBuffer));
+    base::unique_fd fd(open("/dev/null", O_RDONLY));
     EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .WillRepeatedly([&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>&,
                                 const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
-                return ftl::yield<FenceResult>(Fence::NO_FENCE);
+                return ftl::yield<FenceResult>(sp<Fence>::make(std::move(fd)));
             });
 
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(true));
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
     verify().execute().expectAFenceWasReturned();
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
 
@@ -4018,7 +4233,6 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
        usesExpectedDisplaySettingsWithFp16Buffer) {
     SET_FLAG_FOR_TEST(flags::fp16_client_target, true);
-    ALOGE("alecmouri: %d", flags::fp16_client_target());
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kDisplayLuminance)
@@ -4093,7 +4307,12 @@
 };
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) {
-    mOutput.mState.isSecure = true;
+    SET_FLAG_FOR_TEST(flags::protected_if_client, true);
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = false;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -4107,7 +4326,12 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) {
-    mOutput.mState.isSecure = true;
+    SET_FLAG_FOR_TEST(flags::protected_if_client, true);
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
 
@@ -4129,7 +4353,12 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEverywhere) {
-    mOutput.mState.isSecure = true;
+    SET_FLAG_FOR_TEST(flags::protected_if_client, true);
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -4142,7 +4371,12 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) {
-    mOutput.mState.isSecure = true;
+    SET_FLAG_FOR_TEST(flags::protected_if_client, true);
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -4221,6 +4455,7 @@
 
     GenerateClientCompositionRequestsTest() {
         mOutput.mState.needsFiltering = false;
+        mOutput.mState.isProtected = true;
 
         mOutput.setDisplayColorProfileForTest(
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
@@ -4245,6 +4480,7 @@
         mOutput.mState.displaySpace.setOrientation(kDisplayOrientation);
         mOutput.mState.needsFiltering = false;
         mOutput.mState.isSecure = false;
+        mOutput.mState.isProtected = true;
 
         for (size_t i = 0; i < mLayers.size(); i++) {
             mLayers[i].mOutputLayerState.clearClientTarget = false;
@@ -4707,7 +4943,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4720,7 +4956,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4733,7 +4969,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4911,7 +5147,7 @@
             Region(Rect(0, 0, 1000, 1000)),
             false, /* needs filtering */
             true,  /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kPortraitViewport,
             kOutputDataspace,
             true /* realContentIsVisible */,
@@ -4930,7 +5166,7 @@
             Region(Rect(1000, 0, 2000, 1000)),
             false, /* needs filtering */
             true,  /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kPortraitViewport,
             kOutputDataspace,
             true /* realContentIsVisible */,
@@ -5122,7 +5358,12 @@
 };
 
 TEST_F(OutputUpdateProtectedContentStateTest, ifProtectedContentLayerComposeByHWC) {
-    mOutput.mState.isSecure = true;
+    SET_FLAG_FOR_TEST(flags::protected_if_client, true);
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer1.mLayerFEState.hasProtectedContent = false;
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
@@ -5133,7 +5374,12 @@
 }
 
 TEST_F(OutputUpdateProtectedContentStateTest, ifProtectedContentLayerComposeByClient) {
-    mOutput.mState.isSecure = true;
+    SET_FLAG_FOR_TEST(flags::protected_if_client, true);
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer1.mLayerFEState.hasProtectedContent = false;
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 763b998..54ee0ef 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -244,7 +244,7 @@
 
 TEST_F(FlattenerTest, unflattenLayers_onlySourceCropMoved) {
     SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
-                              cache_if_source_crop_layer_only_moved,
+                              cache_when_source_crop_layer_only_moved,
                       true);
 
     auto& layerState1 = mTestLayers[0]->layerState;
@@ -1339,6 +1339,55 @@
     EXPECT_EQ(nullptr, overrideBuffer3);
 }
 
+TEST_F(FlattenerTest, flattenLayers_skipsLayersDisablingDimming) {
+    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 disables dimming, which means it should not be cached
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[2]->layerFECompositionState.dimmingEnabled = false;
+    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(ftl::yield<FenceResult>(Fence::NO_FENCE))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
+
+    // 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, true);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
 TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) {
     auto& layerState1 = mTestLayers[0]->layerState;
     const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 044917e..03758b3 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "LayerStateTest"
 
 #include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <common/include/common/test/FlagUtils.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/planner/LayerState.h>
 #include <compositionengine/mock/LayerFE.h>
@@ -26,6 +27,7 @@
 #include <log/log.h>
 
 #include "android/hardware_buffer.h"
+#include "com_android_graphics_surfaceflinger_flags.h"
 #include "compositionengine/LayerFECompositionState.h"
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
@@ -304,6 +306,16 @@
     EXPECT_EQ(Composition::CLIENT, mLayerState->getCompositionType());
 }
 
+TEST_F(LayerStateTest, getHdrSdrRatio) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.currentHdrSdrRatio = 2.f;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    EXPECT_EQ(2.f, mLayerState->getHdrSdrRatio());
+}
+
 TEST_F(LayerStateTest, updateCompositionType) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
@@ -454,6 +466,9 @@
 }
 
 TEST_F(LayerStateTest, compareSourceCrop) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.sourceCrop = sFloatRectOne;
     LayerFECompositionState layerFECompositionState;
@@ -1033,6 +1048,47 @@
     EXPECT_TRUE(otherLayerState->compare(*mLayerState));
 }
 
+TEST_F(LayerStateTest, updateDimmingEnabled) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.dimmingEnabled = true;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    EXPECT_TRUE(mLayerState->isDimmingEnabled());
+
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.dimmingEnabled = false;
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::DimmingEnabled), updates);
+    EXPECT_FALSE(mLayerState->isDimmingEnabled());
+}
+
+TEST_F(LayerStateTest, compareDimmingEnabled) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.dimmingEnabled = true;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.dimmingEnabled = false;
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+    verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::DimmingEnabled);
+
+    EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+    EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
 TEST_F(LayerStateTest, dumpDoesNotCrash) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
index 35d0ffb..a1210b4 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
@@ -18,6 +18,9 @@
 #undef LOG_TAG
 #define LOG_TAG "PredictorTest"
 
+#include <common/include/common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
+
 #include <compositionengine/impl/planner/Predictor.h>
 #include <compositionengine/mock/LayerFE.h>
 #include <compositionengine/mock/OutputLayer.h>
@@ -127,6 +130,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -158,6 +164,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -304,6 +313,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -347,6 +359,9 @@
 };
 
 TEST_F(LayerStackTest, reorderingChangesNonBufferHash) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -467,6 +482,9 @@
 }
 
 TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -504,6 +522,9 @@
 }
 
 TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp
new file mode 100644
index 0000000..f093384
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplayModeController.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 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 "DisplayModeController"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "Display/DisplayModeController.h"
+#include "Display/DisplaySnapshot.h"
+
+#include <log/log.h>
+
+namespace android::display {
+
+void DisplayModeController::registerDisplay(DisplaySnapshotRef snapshotRef,
+                                            DisplayModeId activeModeId,
+                                            scheduler::RefreshRateSelector::Config config) {
+    const auto& snapshot = snapshotRef.get();
+    const auto displayId = snapshot.displayId();
+
+    mDisplays.emplace_or_replace(displayId, snapshotRef, snapshot.displayModes(), activeModeId,
+                                 config);
+}
+
+void DisplayModeController::unregisterDisplay(PhysicalDisplayId displayId) {
+    const bool ok = mDisplays.erase(displayId);
+    ALOGE_IF(!ok, "%s: Unknown display %s", __func__, to_string(displayId).c_str());
+}
+
+auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) -> RefreshRateSelectorPtr {
+    return mDisplays.get(displayId)
+            .transform([](const Display& display) { return display.selectorPtr; })
+            .value_or(nullptr);
+}
+
+} // namespace android::display
diff --git a/services/surfaceflinger/Display/DisplayModeController.h b/services/surfaceflinger/Display/DisplayModeController.h
new file mode 100644
index 0000000..b6a6bee
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplayModeController.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 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 <memory>
+#include <utility>
+
+#include <android-base/thread_annotations.h>
+#include <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
+
+#include "Display/DisplaySnapshotRef.h"
+#include "DisplayHardware/DisplayMode.h"
+#include "Scheduler/RefreshRateSelector.h"
+#include "ThreadContext.h"
+
+namespace android::display {
+
+// Selects the DisplayMode of each physical display, in accordance with DisplayManager policy and
+// certain heuristic signals.
+class DisplayModeController {
+public:
+    // The referenced DisplaySnapshot must outlive the registration.
+    void registerDisplay(DisplaySnapshotRef, DisplayModeId, scheduler::RefreshRateSelector::Config)
+            REQUIRES(kMainThreadContext);
+    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext);
+
+    // TODO(b/241285876): Remove once ownership is no longer shared with DisplayDevice.
+    using RefreshRateSelectorPtr = std::shared_ptr<scheduler::RefreshRateSelector>;
+
+    // Returns `nullptr` if the display is no longer registered (or never was).
+    RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) REQUIRES(kMainThreadContext);
+
+    // Used by tests to inject an existing RefreshRateSelector.
+    // TODO(b/241285876): Remove this.
+    void registerDisplay(PhysicalDisplayId displayId, DisplaySnapshotRef snapshotRef,
+                         RefreshRateSelectorPtr selectorPtr) {
+        mDisplays.emplace_or_replace(displayId, snapshotRef, selectorPtr);
+    }
+
+private:
+    struct Display {
+        Display(DisplaySnapshotRef snapshot, RefreshRateSelectorPtr selectorPtr)
+              : snapshot(snapshot), selectorPtr(std::move(selectorPtr)) {}
+
+        Display(DisplaySnapshotRef snapshot, DisplayModes modes, DisplayModeId activeModeId,
+                scheduler::RefreshRateSelector::Config config)
+              : Display(snapshot,
+                        std::make_shared<scheduler::RefreshRateSelector>(std::move(modes),
+                                                                         activeModeId, config)) {}
+
+        const DisplaySnapshotRef snapshot;
+        const RefreshRateSelectorPtr selectorPtr;
+    };
+
+    ui::PhysicalDisplayMap<PhysicalDisplayId, Display> mDisplays;
+};
+
+} // namespace android::display
diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
index d07cdf5..ec3ec52 100644
--- a/services/surfaceflinger/Display/DisplayModeRequest.h
+++ b/services/surfaceflinger/Display/DisplayModeRequest.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android-base/stringprintf.h>
 #include <ftl/non_null.h>
 
 #include <scheduler/FrameRateMode.h>
@@ -27,10 +28,19 @@
 
     // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE.
     bool emitEvent = false;
+
+    // Whether to force the request to be applied, even if the mode is unchanged.
+    bool force = false;
 };
 
 inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) {
     return lhs.mode == rhs.mode && lhs.emitEvent == rhs.emitEvent;
 }
 
+inline std::string to_string(const DisplayModeRequest& request) {
+    constexpr const char* kBool[] = {"false", "true"};
+    return base::StringPrintf("{mode=%s, emitEvent=%s, force=%s}", to_string(request.mode).c_str(),
+                              kBool[request.emitEvent], kBool[request.force]);
+}
+
 } // namespace android::display
diff --git a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl b/services/surfaceflinger/Display/DisplaySnapshotRef.h
similarity index 75%
rename from libs/gui/aidl/android/gui/LayerDebugInfo.aidl
rename to services/surfaceflinger/Display/DisplaySnapshotRef.h
index faca980..6cc5f7e 100644
--- a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl
+++ b/services/surfaceflinger/Display/DisplaySnapshotRef.h
@@ -14,6 +14,14 @@
  * limitations under the License.
  */
 
-package android.gui;
+#pragma once
 
-parcelable LayerDebugInfo cpp_header "gui/LayerDebugInfo.h";
+#include <functional>
+
+namespace android::display {
+
+class DisplaySnapshot;
+
+using DisplaySnapshotRef = std::reference_wrapper<const DisplaySnapshot>;
+
+} // namespace android::display
diff --git a/services/surfaceflinger/Display/PhysicalDisplay.h b/services/surfaceflinger/Display/PhysicalDisplay.h
index ef36234..9b1f1ed 100644
--- a/services/surfaceflinger/Display/PhysicalDisplay.h
+++ b/services/surfaceflinger/Display/PhysicalDisplay.h
@@ -25,6 +25,7 @@
 #include <utils/StrongPointer.h>
 
 #include "DisplaySnapshot.h"
+#include "DisplaySnapshotRef.h"
 
 namespace android::display {
 
@@ -45,8 +46,7 @@
 
     // Transformers for PhysicalDisplays::get.
 
-    using SnapshotRef = std::reference_wrapper<const DisplaySnapshot>;
-    SnapshotRef snapshotRef() const { return std::cref(mSnapshot); }
+    DisplaySnapshotRef snapshotRef() const { return std::cref(mSnapshot); }
 
     bool isInternal() const {
         return mSnapshot.connectionType() == ui::DisplayConnectionType::Internal;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 950b05e..38cf053 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -24,6 +24,7 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -67,6 +68,7 @@
         mActiveModeFpsTrace(concatId("ActiveModeFps")),
         mRenderRateFpsTrace(concatId("RenderRateFps")),
         mPhysicalOrientation(args.physicalOrientation),
+        mPowerMode(ftl::Concat("PowerMode ", getId().value).c_str(), args.initialPowerMode),
         mIsPrimary(args.isPrimary),
         mRequestedRefreshRate(args.requestedRefreshRate),
         mRefreshRateSelector(std::move(args.refreshRateSelector)),
@@ -105,9 +107,7 @@
 
     mCompositionDisplay->getRenderSurface()->initialize();
 
-    if (const auto powerModeOpt = args.initialPowerMode) {
-        setPowerMode(*powerModeOpt);
-    }
+    setPowerMode(args.initialPowerMode);
 
     // initialize the display orientation transform.
     setProjection(ui::ROTATION_0, Rect::INVALID_RECT, Rect::INVALID_RECT);
@@ -137,7 +137,7 @@
 
 auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo {
     gui::DisplayInfo info;
-    info.displayId = getLayerStack().id;
+    info.displayId = ui::LogicalDisplayId{static_cast<int32_t>(getLayerStack().id)};
 
     // The physical orientation is set when the orientation of the display panel is
     // different than the default orientation of the device. Other services like
@@ -172,6 +172,7 @@
 }
 
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
+    // TODO(b/241285876): Skip this for virtual displays.
     if (mode == hal::PowerMode::OFF || mode == hal::PowerMode::ON) {
         if (mStagedBrightness && mBrightness != mStagedBrightness) {
             getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
@@ -181,33 +182,26 @@
         getCompositionDisplay()->applyDisplayBrightness(true);
     }
 
-    if (mPowerMode) {
-        *mPowerMode = mode;
-    } else {
-        mPowerMode.emplace("PowerMode -" + to_string(getId()), mode);
-    }
+    mPowerMode = mode;
 
     getCompositionDisplay()->setCompositionEnabled(isPoweredOn());
 }
 
 void DisplayDevice::tracePowerMode() {
-    // assign the same value for tracing
-    if (mPowerMode) {
-        const hal::PowerMode powerMode = *mPowerMode;
-        *mPowerMode = powerMode;
-    }
+    // Assign the same value for tracing.
+    mPowerMode = mPowerMode.get();
 }
 
 void DisplayDevice::enableLayerCaching(bool enable) {
     getCompositionDisplay()->setLayerCachingEnabled(enable);
 }
 
-std::optional<hal::PowerMode> DisplayDevice::getPowerMode() const {
+hal::PowerMode DisplayDevice::getPowerMode() const {
     return mPowerMode;
 }
 
 bool DisplayDevice::isPoweredOn() const {
-    return mPowerMode && *mPowerMode != hal::PowerMode::OFF;
+    return mPowerMode != hal::PowerMode::OFF;
 }
 
 void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
@@ -221,6 +215,17 @@
 bool DisplayDevice::initiateModeChange(display::DisplayModeRequest&& desiredMode,
                                        const hal::VsyncPeriodChangeConstraints& constraints,
                                        hal::VsyncPeriodChangeTimeline& outTimeline) {
+    // TODO(b/255635711): Flow the DisplayModeRequest through the desired/pending/active states. For
+    // now, `desiredMode` and `mDesiredModeOpt` are one and the same, but the latter is not cleared
+    // until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been consumed
+    // at this point, so clear the `force` flag to prevent an endless loop of `initiateModeChange`.
+    if (FlagManager::getInstance().connected_display()) {
+        std::scoped_lock lock(mDesiredModeLock);
+        if (mDesiredModeOpt) {
+            mDesiredModeOpt->force = false;
+        }
+    }
+
     mPendingModeOpt = std::move(desiredMode);
     mIsModeSetPending = true;
 
@@ -246,10 +251,8 @@
         return 0;
     }
 
-    nsecs_t vsyncPeriod;
-    const auto status = mHwComposer.getDisplayVsyncPeriod(physicalId, &vsyncPeriod);
-    if (status == NO_ERROR) {
-        return vsyncPeriod;
+    if (const auto vsyncPeriodOpt = mHwComposer.getDisplayVsyncPeriod(physicalId).value_opt()) {
+        return *vsyncPeriodOpt;
     }
 
     return refreshRateSelector().getActiveMode().modePtr->getVsyncRate().getPeriodNsecs();
@@ -353,6 +356,10 @@
     return mCompositionDisplay->isSecure();
 }
 
+void DisplayDevice::setSecure(bool secure) {
+    mCompositionDisplay->setSecure(secure);
+}
+
 const Rect DisplayDevice::getBounds() const {
     return mCompositionDisplay->getState().displaySpace.getBoundsAsRect();
 }
@@ -478,11 +485,11 @@
     }
 }
 
-void DisplayDevice::updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc) {
+void DisplayDevice::updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc) {
     ATRACE_CALL();
     if (mRefreshRateOverlay) {
         if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
-            mRefreshRateOverlay->changeRefreshRate(vsyncRate, renderFps);
+            mRefreshRateOverlay->changeRefreshRate(refreshRate, renderFps);
         } else {
             mRefreshRateOverlay->changeRenderRate(renderFps);
         }
@@ -522,29 +529,27 @@
     }
 }
 
-auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode, bool force)
-        -> DesiredModeAction {
-    ATRACE_CALL();
-
-    const auto& desiredModePtr = desiredMode.mode.modePtr;
-
-    LOG_ALWAYS_FATAL_IF(getPhysicalId() != desiredModePtr->getPhysicalDisplayId(),
-                        "DisplayId mismatch");
-
-    ALOGV("%s(%s)", __func__, to_string(*desiredModePtr).c_str());
+auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> DesiredModeAction {
+    ATRACE_NAME(concatId(__func__).c_str());
+    ALOGD("%s %s", concatId(__func__).c_str(), to_string(desiredMode).c_str());
 
     std::scoped_lock lock(mDesiredModeLock);
     if (mDesiredModeOpt) {
         // A mode transition was already scheduled, so just override the desired mode.
         const bool emitEvent = mDesiredModeOpt->emitEvent;
+        const bool force = mDesiredModeOpt->force;
         mDesiredModeOpt = std::move(desiredMode);
         mDesiredModeOpt->emitEvent |= emitEvent;
+        if (FlagManager::getInstance().connected_display()) {
+            mDesiredModeOpt->force |= force;
+        }
         return DesiredModeAction::None;
     }
 
     // If the desired mode is already active...
     const auto activeMode = refreshRateSelector().getActiveMode();
-    if (!force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
+    if (const auto& desiredModePtr = desiredMode.mode.modePtr;
+        !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
         if (activeMode == desiredMode.mode) {
             return DesiredModeAction::None;
         }
@@ -555,10 +560,8 @@
         return DesiredModeAction::InitiateRenderRateSwitch;
     }
 
-    // Set the render frame rate to the active physical refresh rate to schedule the next
-    // frame as soon as possible.
     setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
-                  activeMode.modePtr->getVsyncRate());
+                  activeMode.modePtr->getPeakFps());
 
     // Initiate a mode change.
     mDesiredModeOpt = std::move(desiredMode);
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index ac390cb..b339bc6 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -95,6 +95,7 @@
     // isSecure indicates whether this display can be trusted to display
     // secure surfaces.
     bool isSecure() const;
+    void setSecure(bool secure);
 
     int getWidth() const;
     int getHeight() const;
@@ -172,8 +173,8 @@
     /* ------------------------------------------------------------------------
      * Display power mode management.
      */
-    std::optional<hardware::graphics::composer::hal::PowerMode> getPowerMode() const;
-    void setPowerMode(hardware::graphics::composer::hal::PowerMode mode);
+    hardware::graphics::composer::hal::PowerMode getPowerMode() const;
+    void setPowerMode(hardware::graphics::composer::hal::PowerMode);
     bool isPoweredOn() const;
     void tracePowerMode();
 
@@ -188,8 +189,7 @@
 
     enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
 
-    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&, bool force = false)
-            EXCLUDES(mDesiredModeLock);
+    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&) EXCLUDES(mDesiredModeLock);
 
     using DisplayModeRequestOpt = ftl::Optional<display::DisplayModeRequest>;
 
@@ -226,7 +226,7 @@
     // Enables an overlay to be displayed with the current refresh rate
     void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate,
                                   bool showInMiddle) REQUIRES(kMainThreadContext);
-    void updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc = false);
+    void updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc = false);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
 
@@ -270,9 +270,7 @@
     ui::Rotation mOrientation = ui::ROTATION_0;
     bool mIsOrientationChanged = false;
 
-    // Allow nullopt as initial power mode.
-    using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
-    std::optional<TracedPowerMode> mPowerMode;
+    TracedOrdinal<hardware::graphics::composer::hal::PowerMode> mPowerMode;
 
     std::optional<float> mStagedBrightness;
     std::optional<float> mBrightness;
@@ -331,6 +329,7 @@
     uint32_t width = 0;
     uint32_t height = 0;
     std::string displayName;
+    std::string uniqueId;
     bool isSecure = false;
     bool isProtected = false;
     // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
@@ -362,9 +361,9 @@
     HdrCapabilities hdrCapabilities;
     int32_t supportedPerFrameMetadata{0};
     std::unordered_map<ui::ColorMode, std::vector<ui::RenderIntent>> hwcColorModes;
-    std::optional<hardware::graphics::composer::hal::PowerMode> initialPowerMode;
+    hardware::graphics::composer::hal::PowerMode initialPowerMode{
+            hardware::graphics::composer::hal::PowerMode::OFF};
     bool isPrimary{false};
-    DisplayModeId activeModeId;
     // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
     Fps requestedRefreshRate;
 };
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 6250b11..362ab9c 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -330,7 +330,11 @@
 
     t.join();
     close(pipefds[0]);
-    return str;
+
+    std::string hash;
+    mAidlComposer->getInterfaceHash(&hash);
+    return std::string(mAidlComposer->descriptor) +
+            " version:" + std::to_string(mComposerInterfaceVersion) + " hash:" + hash + str;
 }
 
 void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) {
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index ba0825c..224f50e 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -21,17 +21,17 @@
 
 #include <android-base/stringprintf.h>
 #include <android/configuration.h>
+#include <ftl/mixins.h>
 #include <ftl/small_map.h>
 #include <ui/DisplayId.h>
 #include <ui/DisplayMode.h>
 #include <ui/Size.h>
 #include <utils/Timers.h>
 
+#include <common/FlagManager.h>
 #include <scheduler/Fps.h>
 
-#include <common/FlagManager.h>
 #include "DisplayHardware/Hal.h"
-#include "Scheduler/StrongTyping.h"
 
 namespace android {
 
@@ -46,7 +46,12 @@
 bool operator<=(const DisplayModePtr&, const DisplayModePtr&) = delete;
 bool operator>=(const DisplayModePtr&, const DisplayModePtr&) = delete;
 
-using DisplayModeId = StrongTyping<ui::DisplayModeId, struct DisplayModeIdTag, Compare>;
+struct DisplayModeId : ftl::DefaultConstructible<DisplayModeId, ui::DisplayModeId>,
+                       ftl::Incrementable<DisplayModeId>,
+                       ftl::Equatable<DisplayModeId>,
+                       ftl::Orderable<DisplayModeId> {
+    using DefaultConstructible::DefaultConstructible;
+};
 
 using DisplayModes = ftl::SmallMap<DisplayModeId, DisplayModePtr, 3>;
 using DisplayModeIterator = DisplayModes::const_iterator;
@@ -185,7 +190,7 @@
 inline std::string to_string(const DisplayMode& mode) {
     return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, vsyncRate=%s, "
                               "dpi=%.2fx%.2f, group=%d, vrrConfig=%s}",
-                              mode.getId().value(), mode.getHwcId(), mode.getWidth(),
+                              ftl::to_underlying(mode.getId()), mode.getHwcId(), mode.getWidth(),
                               mode.getHeight(), to_string(mode.getVsyncRate()).c_str(),
                               mode.getDpi().x, mode.getDpi().y, mode.getGroup(),
                               to_string(mode.getVrrConfig()).c_str());
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 704ece5..84f668d 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -27,6 +27,7 @@
 #include "HWC2.h"
 
 #include <android/configuration.h>
+#include <common/FlagManager.h>
 #include <ui/Fence.h>
 #include <ui/FloatRect.h>
 #include <ui/GraphicBuffer.h>
@@ -281,19 +282,28 @@
     return Error::NONE;
 }
 
-Error Display::getConnectionType(ui::DisplayConnectionType* outType) const {
-    if (mType != DisplayType::PHYSICAL) return Error::BAD_DISPLAY;
+ftl::Expected<ui::DisplayConnectionType, hal::Error> Display::getConnectionType() const {
+    if (!mConnectionType) {
+        mConnectionType = [this]() -> decltype(mConnectionType) {
+            if (mType != DisplayType::PHYSICAL) {
+                return ftl::Unexpected(Error::BAD_DISPLAY);
+            }
 
-    using ConnectionType = Hwc2::IComposerClient::DisplayConnectionType;
-    ConnectionType connectionType;
-    const auto error = static_cast<Error>(mComposer.getDisplayConnectionType(mId, &connectionType));
-    if (error != Error::NONE) {
-        return error;
+            using ConnectionType = Hwc2::IComposerClient::DisplayConnectionType;
+            ConnectionType connectionType;
+
+            if (const auto error = static_cast<Error>(
+                        mComposer.getDisplayConnectionType(mId, &connectionType));
+                error != Error::NONE) {
+                return ftl::Unexpected(error);
+            }
+
+            return connectionType == ConnectionType::INTERNAL ? ui::DisplayConnectionType::Internal
+                                                              : ui::DisplayConnectionType::External;
+        }();
     }
 
-    *outType = connectionType == ConnectionType::INTERNAL ? ui::DisplayConnectionType::Internal
-                                                          : ui::DisplayConnectionType::External;
-    return Error::NONE;
+    return *mConnectionType;
 }
 
 bool Display::hasCapability(DisplayCapability capability) const {
@@ -416,7 +426,14 @@
                                               VsyncPeriodChangeTimeline* outTimeline) {
     ALOGV("[%" PRIu64 "] setActiveConfigWithConstraints", mId);
 
-    if (isVsyncPeriodSwitchSupported()) {
+    // FIXME (b/319505580): At least the first config set on an external display must be
+    // `setActiveConfig`, so skip over the block that calls `setActiveConfigWithConstraints`
+    // for simplicity.
+    const bool connected_display = FlagManager::getInstance().connected_display();
+
+    if (isVsyncPeriodSwitchSupported() &&
+        (!connected_display ||
+         getConnectionType().value_opt() != ui::DisplayConnectionType::External)) {
         Hwc2::IComposerClient::VsyncPeriodChangeConstraints hwc2Constraints;
         hwc2Constraints.desiredTimeNanos = constraints.desiredTimeNanos;
         hwc2Constraints.seamlessRequired = constraints.seamlessRequired;
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index f907061..de044e0 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -18,6 +18,7 @@
 
 #include <android-base/expected.h>
 #include <android-base/thread_annotations.h>
+#include <ftl/expected.h>
 #include <ftl/future.h>
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
@@ -120,7 +121,8 @@
     [[nodiscard]] virtual hal::Error getRequests(
             hal::DisplayRequest* outDisplayRequests,
             std::unordered_map<Layer*, hal::LayerRequest>* outLayerRequests) = 0;
-    [[nodiscard]] virtual hal::Error getConnectionType(ui::DisplayConnectionType*) const = 0;
+    [[nodiscard]] virtual ftl::Expected<ui::DisplayConnectionType, hal::Error> getConnectionType()
+            const = 0;
     [[nodiscard]] virtual hal::Error supportsDoze(bool* outSupport) const = 0;
     [[nodiscard]] virtual hal::Error getHdrCapabilities(
             android::HdrCapabilities* outCapabilities) const = 0;
@@ -213,7 +215,7 @@
     hal::Error getRequests(
             hal::DisplayRequest* outDisplayRequests,
             std::unordered_map<HWC2::Layer*, hal::LayerRequest>* outLayerRequests) override;
-    hal::Error getConnectionType(ui::DisplayConnectionType*) const override;
+    ftl::Expected<ui::DisplayConnectionType, hal::Error> getConnectionType() const override;
     hal::Error supportsDoze(bool* outSupport) const override EXCLUDES(mDisplayCapabilitiesMutex);
     hal::Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override;
     hal::Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties*
@@ -294,6 +296,8 @@
 
     const hal::HWDisplayId mId;
     hal::DisplayType mType;
+    // Cached on first call to getConnectionType.
+    mutable std::optional<ftl::Expected<ui::DisplayConnectionType, hal::Error>> mConnectionType;
     bool mIsConnected = false;
 
     using Layers = std::unordered_map<hal::HWLayerId, std::weak_ptr<HWC2::impl::Layer>>;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 3ffd8ea..3cfb9ca 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -77,7 +77,7 @@
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
-namespace hal = android::hardware::graphics::composer::hal;
+using namespace std::string_literals;
 
 namespace android {
 
@@ -89,7 +89,8 @@
       : mComposer(std::move(composer)),
         mMaxVirtualDisplayDimension(static_cast<size_t>(sysprop::max_virtual_display_dimension(0))),
         mUpdateDeviceProductInfoOnHotplugReconnect(
-                sysprop::update_device_product_info_on_hotplug_reconnect(false)) {}
+                sysprop::update_device_product_info_on_hotplug_reconnect(false)),
+        mEnableVrrTimeout(base::GetBoolProperty("debug.sf.vrr_timeout_hint_enabled"s, true)) {}
 
 HWComposer::HWComposer(const std::string& composerServiceName)
       : HWComposer(Hwc2::Composer::create(composerServiceName)) {}
@@ -299,6 +300,10 @@
             hwcMode.dpiY = config.dpi->y;
         }
 
+        if (!mEnableVrrTimeout) {
+            hwcMode.vrrConfig->notifyExpectedPresentConfig = {};
+        }
+
         modes.push_back(hwcMode);
     }
 
@@ -336,14 +341,18 @@
     return modes;
 }
 
-std::optional<hal::HWConfigId> HWComposer::getActiveMode(PhysicalDisplayId displayId) const {
-    RETURN_IF_INVALID_DISPLAY(displayId, std::nullopt);
+ftl::Expected<hal::HWConfigId, status_t> HWComposer::getActiveMode(
+        PhysicalDisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, ftl::Unexpected(BAD_INDEX));
     const auto hwcId = *fromPhysicalDisplayId(displayId);
 
     hal::HWConfigId configId;
     const auto error = static_cast<hal::Error>(mComposer->getActiveConfig(hwcId, &configId));
+    if (error == hal::Error::BAD_CONFIG) {
+        return ftl::Unexpected(NO_INIT);
+    }
 
-    RETURN_IF_HWC_ERROR_FOR("getActiveConfig", error, displayId, std::nullopt);
+    RETURN_IF_HWC_ERROR_FOR("getActiveConfig", error, displayId, ftl::Unexpected(UNKNOWN_ERROR));
     return configId;
 }
 
@@ -353,15 +362,13 @@
     RETURN_IF_INVALID_DISPLAY(displayId, ui::DisplayConnectionType::Internal);
     const auto& hwcDisplay = mDisplayData.at(displayId).hwcDisplay;
 
-    ui::DisplayConnectionType type;
-    const auto error = hwcDisplay->getConnectionType(&type);
-
-    const auto FALLBACK_TYPE = hwcDisplay->getId() == mPrimaryHwcDisplayId
-            ? ui::DisplayConnectionType::Internal
-            : ui::DisplayConnectionType::External;
-
-    RETURN_IF_HWC_ERROR(error, displayId, FALLBACK_TYPE);
-    return type;
+    if (const auto connectionType = hwcDisplay->getConnectionType()) {
+        return connectionType.value();
+    } else {
+        LOG_HWC_ERROR(__func__, connectionType.error(), displayId);
+        return hwcDisplay->getId() == mPrimaryHwcDisplayId ? ui::DisplayConnectionType::Internal
+                                                           : ui::DisplayConnectionType::External;
+    }
 }
 
 bool HWComposer::isVsyncPeriodSwitchSupported(PhysicalDisplayId displayId) const {
@@ -369,20 +376,20 @@
     return mDisplayData.at(displayId).hwcDisplay->isVsyncPeriodSwitchSupported();
 }
 
-status_t HWComposer::getDisplayVsyncPeriod(PhysicalDisplayId displayId,
-                                           nsecs_t* outVsyncPeriod) const {
-    RETURN_IF_INVALID_DISPLAY(displayId, 0);
+ftl::Expected<nsecs_t, status_t> HWComposer::getDisplayVsyncPeriod(
+        PhysicalDisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, ftl::Unexpected(BAD_INDEX));
 
     if (!isVsyncPeriodSwitchSupported(displayId)) {
-        return INVALID_OPERATION;
+        return ftl::Unexpected(INVALID_OPERATION);
     }
+
     const auto hwcId = *fromPhysicalDisplayId(displayId);
     Hwc2::VsyncPeriodNanos vsyncPeriodNanos = 0;
-    auto error =
+    const auto error =
             static_cast<hal::Error>(mComposer->getDisplayVsyncPeriod(hwcId, &vsyncPeriodNanos));
-    RETURN_IF_HWC_ERROR(error, displayId, 0);
-    *outVsyncPeriod = static_cast<nsecs_t>(vsyncPeriodNanos);
-    return NO_ERROR;
+    RETURN_IF_HWC_ERROR(error, displayId, ftl::Unexpected(UNKNOWN_ERROR));
+    return static_cast<nsecs_t>(vsyncPeriodNanos);
 }
 
 std::vector<ui::ColorMode> HWComposer::getColorModes(PhysicalDisplayId displayId) const {
@@ -955,8 +962,45 @@
     return mSupportedLayerGenericMetadata;
 }
 
+void HWComposer::dumpOverlayProperties(std::string& result) const {
+    // dump overlay properties
+    result.append("OverlayProperties:\n");
+    base::StringAppendF(&result, "supportMixedColorSpaces: %d\n",
+                        mOverlayProperties.supportMixedColorSpaces);
+    base::StringAppendF(&result, "SupportedBufferCombinations(%zu entries)\n",
+                        mOverlayProperties.combinations.size());
+    for (const auto& combination : mOverlayProperties.combinations) {
+        result.append("    pixelFormats=\n");
+        for (const auto& pixelFormat : combination.pixelFormats) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodePixelFormat(static_cast<PixelFormat>(pixelFormat)).c_str(),
+                                static_cast<uint32_t>(pixelFormat));
+        }
+        result.append("    standards=\n");
+        for (const auto& standard : combination.standards) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeStandardOnly(static_cast<uint32_t>(standard)).c_str(),
+                                static_cast<uint32_t>(standard));
+        }
+        result.append("    transfers=\n");
+        for (const auto& transfer : combination.transfers) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeTransferOnly(static_cast<uint32_t>(transfer)).c_str(),
+                                static_cast<uint32_t>(transfer));
+        }
+        result.append("    ranges=\n");
+        for (const auto& range : combination.ranges) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeRangeOnly(static_cast<uint32_t>(range)).c_str(),
+                                static_cast<uint32_t>(range));
+        }
+        result.append("\n");
+    }
+}
+
 void HWComposer::dump(std::string& result) const {
     result.append(mComposer->dumpDebugInfo());
+    dumpOverlayProperties(result);
 }
 
 std::optional<PhysicalDisplayId> HWComposer::toPhysicalDisplayId(
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 4ca528a..bc32cda 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -25,6 +25,7 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <ftl/expected.h>
 #include <ftl/future.h>
 #include <ui/DisplayIdentification.h>
 #include <ui/FenceTime.h>
@@ -237,7 +238,7 @@
     virtual std::vector<HWCDisplayMode> getModes(PhysicalDisplayId,
                                                  int32_t maxFrameIntervalNs) const = 0;
 
-    virtual std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const = 0;
+    virtual ftl::Expected<hal::HWConfigId, status_t> getActiveMode(PhysicalDisplayId) const = 0;
 
     virtual std::vector<ui::ColorMode> getColorModes(PhysicalDisplayId) const = 0;
 
@@ -247,8 +248,7 @@
     // Composer 2.4
     virtual ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const = 0;
     virtual bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const = 0;
-    virtual status_t getDisplayVsyncPeriod(PhysicalDisplayId displayId,
-                                           nsecs_t* outVsyncPeriod) const = 0;
+    virtual ftl::Expected<nsecs_t, status_t> getDisplayVsyncPeriod(PhysicalDisplayId) const = 0;
     virtual status_t setActiveModeWithConstraints(PhysicalDisplayId, hal::HWConfigId,
                                                   const hal::VsyncPeriodChangeConstraints&,
                                                   hal::VsyncPeriodChangeTimeline* outTimeline) = 0;
@@ -269,6 +269,8 @@
 
     virtual void dump(std::string& out) const = 0;
 
+    virtual void dumpOverlayProperties(std::string& out) const = 0;
+
     virtual Hwc2::Composer* getComposer() const = 0;
 
     // Returns the first display connected at boot. Its connection via HWComposer::onHotplug,
@@ -424,7 +426,7 @@
     std::vector<HWCDisplayMode> getModes(PhysicalDisplayId,
                                          int32_t maxFrameIntervalNs) const override;
 
-    std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const override;
+    ftl::Expected<hal::HWConfigId, status_t> getActiveMode(PhysicalDisplayId) const override;
 
     std::vector<ui::ColorMode> getColorModes(PhysicalDisplayId) const override;
 
@@ -435,8 +437,7 @@
     // Composer 2.4
     ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const override;
     bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const override;
-    status_t getDisplayVsyncPeriod(PhysicalDisplayId displayId,
-                                   nsecs_t* outVsyncPeriod) const override;
+    ftl::Expected<nsecs_t, status_t> getDisplayVsyncPeriod(PhysicalDisplayId) const override;
     status_t setActiveModeWithConstraints(PhysicalDisplayId, hal::HWConfigId,
                                           const hal::VsyncPeriodChangeConstraints&,
                                           hal::VsyncPeriodChangeTimeline* outTimeline) override;
@@ -469,6 +470,7 @@
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
+    void dumpOverlayProperties(std::string& out) const override;
 
     Hwc2::Composer* getComposer() const override { return mComposer.get(); }
 
@@ -491,6 +493,7 @@
 private:
     // For unit tests
     friend TestableSurfaceFlinger;
+    friend HWComposerTest;
 
     struct DisplayData {
         std::unique_ptr<HWC2::Display> hwcDisplay;
@@ -542,6 +545,7 @@
 
     const size_t mMaxVirtualDisplayDimension;
     const bool mUpdateDeviceProductInfoOnHotplugReconnect;
+    bool mEnableVrrTimeout;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 31c2833..e3d9622 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -169,10 +169,8 @@
         out << "}, ";
         out << "notifyExpectedPresentConfig={";
         if (vrrConfig->notifyExpectedPresentConfig) {
-            out << "notifyExpectedPresentHeadsUpNs="
-                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentHeadsUpNs
-                << ", notifyExpectedPresentTimeoutNs="
-                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs;
+            out << "headsUpNs=" << vrrConfig->notifyExpectedPresentConfig->headsUpNs
+                << ", timeoutNs=" << vrrConfig->notifyExpectedPresentConfig->timeoutNs;
         }
         out << "}}";
         return out.str();
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index c4ff9cc..12ab2c2 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -291,6 +291,7 @@
 
 std::string HidlComposer::dumpDebugInfo() {
     std::string info;
+    info += std::string(mComposer->descriptor) + "\n";
     mComposer->dumpDebugInfo([&](const auto& tmpInfo) { info = tmpInfo.c_str(); });
 
     return info;
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index dd228b4..6c1a813 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -31,10 +31,6 @@
 #include <utils/Mutex.h>
 #include <utils/Trace.h>
 
-#include <aidl/android/hardware/power/IPower.h>
-#include <aidl/android/hardware/power/IPowerHintSession.h>
-#include <aidl/android/hardware/power/WorkDuration.h>
-
 #include <binder/IServiceManager.h>
 
 #include "../SurfaceFlingerProperties.h"
@@ -50,9 +46,21 @@
 namespace impl {
 
 using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::ChannelConfig;
 using aidl::android::hardware::power::Mode;
 using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionTag;
 using aidl::android::hardware::power::WorkDuration;
+using aidl::android::hardware::power::WorkDurationFixedV1;
+
+using aidl::android::hardware::common::fmq::MQDescriptor;
+using aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using aidl::android::hardware::power::ChannelMessage;
+using android::hardware::EventFlag;
+
+using ChannelMessageContents = ChannelMessage::ChannelMessageContents;
+using MsgQueue = android::AidlMessageQueue<ChannelMessage, SynchronizedReadWrite>;
+using FlagQueue = android::AidlMessageQueue<int8_t, SynchronizedReadWrite>;
 
 PowerAdvisor::~PowerAdvisor() = default;
 
@@ -143,15 +151,7 @@
     if (!mBootFinished.load()) {
         return;
     }
-    if (usePowerHintSession()) {
-        std::lock_guard lock(mHintSessionMutex);
-        if (ensurePowerHintSessionRunning()) {
-            auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_UP);
-            if (!ret.isOk()) {
-                mHintSession = nullptr;
-            }
-        }
-    }
+    sendHintSessionHint(SessionHint::CPU_LOAD_UP);
 }
 
 void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() {
@@ -163,15 +163,7 @@
 
     if (mSendUpdateImminent.exchange(false)) {
         ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset");
-        if (usePowerHintSession()) {
-            std::lock_guard lock(mHintSessionMutex);
-            if (ensurePowerHintSessionRunning()) {
-                auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET);
-                if (!ret.isOk()) {
-                    mHintSession = nullptr;
-                }
-            }
-        }
+        sendHintSessionHint(SessionHint::CPU_LOAD_RESET);
 
         if (!mHasDisplayUpdateImminent) {
             ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it");
@@ -208,21 +200,99 @@
     return *mSupportsHintSession;
 }
 
+bool PowerAdvisor::shouldCreateSessionWithConfig() {
+    return mSessionConfigSupported && mBootFinished &&
+            FlagManager::getInstance().adpf_use_fmq_channel();
+}
+
+void PowerAdvisor::sendHintSessionHint(SessionHint hint) {
+    if (!mBootFinished || !usePowerHintSession()) {
+        ALOGV("Power hint session is not enabled, skip sending session hint");
+        return;
+    }
+    ATRACE_CALL();
+    if (sTraceHintSessionData) ATRACE_INT("Session hint", static_cast<int>(hint));
+    {
+        std::scoped_lock lock(mHintSessionMutex);
+        if (!ensurePowerHintSessionRunning()) {
+            ALOGV("Hint session not running and could not be started, skip sending session hint");
+            return;
+        }
+        ALOGV("Sending session hint: %d", static_cast<int>(hint));
+        if (!writeHintSessionMessage<ChannelMessageContents::Tag::hint>(&hint, 1)) {
+            auto ret = mHintSession->sendHint(hint);
+            if (!ret.isOk()) {
+                ALOGW("Failed to send session hint with error: %s", ret.errorMessage());
+                mHintSession = nullptr;
+            }
+        }
+    }
+}
+
 bool PowerAdvisor::ensurePowerHintSessionRunning() {
     if (mHintSession == nullptr && !mHintSessionThreadIds.empty() && usePowerHintSession()) {
-        auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
-                                                   mHintSessionThreadIds, mTargetDuration.ns());
-
-        if (ret.isOk()) {
-            mHintSession = ret.value();
+        if (shouldCreateSessionWithConfig()) {
+            auto ret = getPowerHal().createHintSessionWithConfig(getpid(),
+                                                                 static_cast<int32_t>(getuid()),
+                                                                 mHintSessionThreadIds,
+                                                                 mTargetDuration.ns(),
+                                                                 SessionTag::SURFACEFLINGER,
+                                                                 &mSessionConfig);
+            if (ret.isOk()) {
+                mHintSession = ret.value();
+                if (FlagManager::getInstance().adpf_use_fmq_channel_fixed()) {
+                    setUpFmq();
+                }
+            }
+            // If it fails the first time we try, or ever returns unsupported, assume unsupported
+            else if (mFirstConfigSupportCheck || ret.isUnsupported()) {
+                ALOGI("Hint session with config is unsupported, falling back to a legacy session");
+                mSessionConfigSupported = false;
+            }
+            mFirstConfigSupportCheck = false;
+        }
+        // Immediately try original method after, in case the first way returned unsupported
+        if (mHintSession == nullptr && !shouldCreateSessionWithConfig()) {
+            auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
+                                                       mHintSessionThreadIds, mTargetDuration.ns());
+            if (ret.isOk()) {
+                mHintSession = ret.value();
+            }
         }
     }
     return mHintSession != nullptr;
 }
 
+void PowerAdvisor::setUpFmq() {
+    auto&& channelRet = getPowerHal().getSessionChannel(getpid(), static_cast<int32_t>(getuid()));
+    if (!channelRet.isOk()) {
+        ALOGE("Failed to get session channel with error: %s", channelRet.errorMessage());
+        return;
+    }
+    auto& channelConfig = channelRet.value();
+    mMsgQueue = std::make_unique<MsgQueue>(std::move(channelConfig.channelDescriptor), true);
+    LOG_ALWAYS_FATAL_IF(!mMsgQueue->isValid(), "Failed to set up hint session msg queue");
+    LOG_ALWAYS_FATAL_IF(channelConfig.writeFlagBitmask <= 0,
+                        "Invalid flag bit masks found in channel config: writeBitMask(%d)",
+                        channelConfig.writeFlagBitmask);
+    mFmqWriteMask = static_cast<uint32_t>(channelConfig.writeFlagBitmask);
+    if (!channelConfig.eventFlagDescriptor.has_value()) {
+        // For FMQ v1 in Android 15 we will force using shared event flag since the default
+        // no-op FMQ impl in Power HAL v5 will always return a valid channel config with
+        // non-zero masks but no shared flag.
+        mMsgQueue = nullptr;
+        ALOGE("No event flag descriptor found in channel config");
+        return;
+    }
+    mFlagQueue = std::make_unique<FlagQueue>(std::move(*channelConfig.eventFlagDescriptor), true);
+    LOG_ALWAYS_FATAL_IF(!mFlagQueue->isValid(), "Failed to set up hint session flag queue");
+    auto status = EventFlag::createEventFlag(mFlagQueue->getEventFlagWord(), &mEventFlag);
+    LOG_ALWAYS_FATAL_IF(status != OK, "Failed to set up hint session event flag");
+}
+
 void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) {
-    if (!usePowerHintSession()) {
-        ALOGV("Power hint session target duration cannot be set, skipping");
+    if (!mBootFinished || !usePowerHintSession()) {
+        ALOGV("Power hint session is not enabled, skipping target update");
         return;
     }
     ATRACE_CALL();
@@ -230,14 +300,19 @@
         mTargetDuration = targetDuration;
         if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns());
         if (targetDuration == mLastTargetDurationSent) return;
-        std::lock_guard lock(mHintSessionMutex);
-        if (ensurePowerHintSessionRunning()) {
-            ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns());
-            mLastTargetDurationSent = targetDuration;
+        std::scoped_lock lock(mHintSessionMutex);
+        if (!ensurePowerHintSessionRunning()) {
+            ALOGV("Hint session not running and could not be started, skip updating target");
+            return;
+        }
+        ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns());
+        mLastTargetDurationSent = targetDuration;
+        auto target = targetDuration.ns();
+        if (!writeHintSessionMessage<ChannelMessageContents::Tag::targetDuration>(&target, 1)) {
             auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns());
             if (!ret.isOk()) {
                 ALOGW("Failed to set power hint target work duration with error: %s",
-                      ret.getDescription().c_str());
+                      ret.errorMessage());
                 mHintSession = nullptr;
             }
         }
@@ -250,27 +325,30 @@
         return;
     }
     ATRACE_CALL();
-    std::optional<Duration> actualDuration = estimateWorkDuration();
-    if (!actualDuration.has_value() || actualDuration < 0ns) {
+    std::optional<WorkDuration> actualDuration = estimateWorkDuration();
+    if (!actualDuration.has_value() || actualDuration->durationNanos < 0) {
         ALOGV("Failed to send actual work duration, skipping");
         return;
     }
-    actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
-    mActualDuration = actualDuration;
-
+    actualDuration->durationNanos += sTargetSafetyMargin.ns();
     if (sTraceHintSessionData) {
-        ATRACE_INT64("Measured duration", actualDuration->ns());
-        ATRACE_INT64("Target error term", Duration{*actualDuration - mTargetDuration}.ns());
-        ATRACE_INT64("Reported duration", actualDuration->ns());
+        ATRACE_INT64("Measured duration", actualDuration->durationNanos);
+        ATRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
+        ATRACE_INT64("Reported duration", actualDuration->durationNanos);
+        if (supportsGpuReporting()) {
+            ATRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
+            ATRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
+        }
         ATRACE_INT64("Reported target", mLastTargetDurationSent.ns());
         ATRACE_INT64("Reported target error term",
-                     Duration{*actualDuration - mLastTargetDurationSent}.ns());
+                     actualDuration->durationNanos - mLastTargetDurationSent.ns());
     }
 
-    ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
-          " with error: %" PRId64,
-          actualDuration->ns(), mLastTargetDurationSent.ns(),
-          Duration{*actualDuration - mLastTargetDurationSent}.ns());
+    ALOGV("Sending actual work duration of: %" PRId64 " with cpu: %" PRId64 " and gpu: %" PRId64
+          " on reported target: %" PRId64 " with error: %" PRId64,
+          actualDuration->durationNanos, actualDuration->cpuDurationNanos,
+          actualDuration->gpuDurationNanos, mLastTargetDurationSent.ns(),
+          actualDuration->durationNanos - mLastTargetDurationSent.ns());
 
     if (mTimingTestingMode) {
         mDelayReportActualMutexAcquisitonPromise.get_future().wait();
@@ -278,34 +356,73 @@
     }
 
     {
-        std::lock_guard lock(mHintSessionMutex);
+        std::scoped_lock lock(mHintSessionMutex);
         if (!ensurePowerHintSessionRunning()) {
-            ALOGV("Hint session not running and could not be started, skipping");
+            ALOGV("Hint session not running and could not be started, skip reporting durations");
             return;
         }
-
-        WorkDuration duration{
-                .timeStampNanos = TimePoint::now().ns(),
-                // TODO(b/284324521): Correctly calculate total duration.
-                .durationNanos = actualDuration->ns(),
-                .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
-                .cpuDurationNanos = actualDuration->ns(),
-                // TODO(b/284324521): Calculate RenderEngine GPU time.
-                .gpuDurationNanos = 0,
-        };
-        mHintSessionQueue.push_back(duration);
-
-        auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
-        if (!ret.isOk()) {
-            ALOGW("Failed to report actual work durations with error: %s",
-                  ret.getDescription().c_str());
-            mHintSession = nullptr;
-            return;
+        mHintSessionQueue.push_back(*actualDuration);
+        if (!writeHintSessionMessage<
+                    ChannelMessageContents::Tag::workDuration>(mHintSessionQueue.data(),
+                                                               mHintSessionQueue.size())) {
+            auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
+            if (!ret.isOk()) {
+                ALOGW("Failed to report actual work durations with error: %s", ret.errorMessage());
+                mHintSession = nullptr;
+                return;
+            }
         }
     }
     mHintSessionQueue.clear();
 }
 
+template <ChannelMessage::ChannelMessageContents::Tag T, class In>
+bool PowerAdvisor::writeHintSessionMessage(In* contents, size_t count) {
+    if (!mMsgQueue) {
+        ALOGV("Skip using FMQ with message tag %hhd as it's not supported", T);
+        return false;
+    }
+    auto availableSize = mMsgQueue->availableToWrite();
+    if (availableSize < count) {
+        ALOGW("Skip using FMQ with message tag %hhd as there isn't enough space", T);
+        return false;
+    }
+    MsgQueue::MemTransaction tx;
+    if (!mMsgQueue->beginWrite(count, &tx)) {
+        ALOGW("Failed to begin writing message with tag %hhd", T);
+        return false;
+    }
+    for (size_t i = 0; i < count; ++i) {
+        if constexpr (T == ChannelMessageContents::Tag::workDuration) {
+            const WorkDuration& duration = contents[i];
+            new (tx.getSlot(i)) ChannelMessage{
+                    .sessionID = static_cast<int32_t>(mSessionConfig.id),
+                    .timeStampNanos =
+                            (i == count - 1) ? ::android::uptimeNanos() : duration.timeStampNanos,
+                    .data = ChannelMessageContents::make<ChannelMessageContents::Tag::workDuration,
+                                                         WorkDurationFixedV1>({
+                            .durationNanos = duration.durationNanos,
+                            .workPeriodStartTimestampNanos = duration.workPeriodStartTimestampNanos,
+                            .cpuDurationNanos = duration.cpuDurationNanos,
+                            .gpuDurationNanos = duration.gpuDurationNanos,
+                    }),
+            };
+        } else {
+            new (tx.getSlot(i)) ChannelMessage{
+                    .sessionID = static_cast<int32_t>(mSessionConfig.id),
+                    .timeStampNanos = ::android::uptimeNanos(),
+                    .data = ChannelMessageContents::make<T, In>(std::move(contents[i])),
+            };
+        }
+    }
+    if (!mMsgQueue->commitWrite(count)) {
+        ALOGW("Failed to send message with tag %hhd, fall back to binder call", T);
+        return false;
+    }
+    mEventFlag->wake(mFmqWriteMask);
+    return true;
+}
+
 void PowerAdvisor::enablePowerHintSession(bool enabled) {
     mHintSessionEnabled = enabled;
 }
@@ -321,19 +438,50 @@
     }
     LOG_ALWAYS_FATAL_IF(mHintSessionThreadIds.empty(),
                         "No thread IDs provided to power hint session!");
-    std::lock_guard lock(mHintSessionMutex);
-    if (mHintSession != nullptr) {
-        ALOGE("Cannot start power hint session: already running");
-        return false;
+    {
+        std::scoped_lock lock(mHintSessionMutex);
+        if (mHintSession != nullptr) {
+            ALOGE("Cannot start power hint session: already running");
+            return false;
+        }
+        return ensurePowerHintSessionRunning();
     }
-    return ensurePowerHintSessionRunning();
 }
 
-void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+bool PowerAdvisor::supportsGpuReporting() {
+    return mBootFinished && FlagManager::getInstance().adpf_gpu_sf();
+}
+
+void PowerAdvisor::setGpuStartTime(DisplayId displayId, TimePoint startTime) {
     DisplayTimingData& displayData = mDisplayTimingData[displayId];
     if (displayData.gpuEndFenceTime) {
         nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
         if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
+            for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
+                if (!otherDisplayData.lastValidGpuStartTime.has_value() ||
+                    !otherDisplayData.lastValidGpuEndTime.has_value())
+                    continue;
+                if ((*otherDisplayData.lastValidGpuStartTime < *displayData.gpuStartTime) &&
+                    (*otherDisplayData.lastValidGpuEndTime > *displayData.gpuStartTime)) {
+                    displayData.lastValidGpuStartTime = *otherDisplayData.lastValidGpuEndTime;
+                    break;
+                }
+            }
+        }
+        displayData.gpuEndFenceTime = nullptr;
+    }
+    displayData.gpuStartTime = startTime;
+}
+
+void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+    DisplayTimingData& displayData = mDisplayTimingData[displayId];
+    if (displayData.gpuEndFenceTime && !supportsGpuReporting()) {
+        nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
+        if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
             for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
                 // If the previous display started before us but ended after we should have
                 // started, then it likely delayed our start time and we must compensate for that.
@@ -346,12 +494,12 @@
                     break;
                 }
             }
-            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
-            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
         }
     }
     displayData.gpuEndFenceTime = std::move(fenceTime);
-    displayData.gpuStartTime = TimePoint::now();
+    if (!supportsGpuReporting()) {
+        displayData.gpuStartTime = TimePoint::now();
+    }
 }
 
 void PowerAdvisor::setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
@@ -372,9 +520,8 @@
     mDisplayTimingData[displayId].skippedValidate = skipped;
 }
 
-void PowerAdvisor::setRequiresClientComposition(DisplayId displayId,
-                                                bool requiresClientComposition) {
-    mDisplayTimingData[displayId].usedClientComposition = requiresClientComposition;
+void PowerAdvisor::setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine) {
+    mDisplayTimingData[displayId].requiresRenderEngine = requiresRenderEngine;
 }
 
 void PowerAdvisor::setExpectedPresentTime(TimePoint expectedPresentTime) {
@@ -382,8 +529,8 @@
 }
 
 void PowerAdvisor::setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) {
-    mLastSfPresentEndTime = presentEndTime;
     mLastPresentFenceTime = presentFenceTime;
+    mLastSfPresentEndTime = presentEndTime;
 }
 
 void PowerAdvisor::setFrameDelay(Duration frameDelayDuration) {
@@ -424,7 +571,7 @@
     return sortedDisplays;
 }
 
-std::optional<Duration> PowerAdvisor::estimateWorkDuration() {
+std::optional<WorkDuration> PowerAdvisor::estimateWorkDuration() {
     if (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull()) {
         return std::nullopt;
     }
@@ -443,11 +590,10 @@
     // used to accumulate gpu time as we iterate over the active displays
     std::optional<TimePoint> estimatedGpuEndTime;
 
-    // The timing info for the previously calculated display, if there was one
-    std::optional<DisplayTimeline> previousDisplayTiming;
     std::vector<DisplayId>&& displayIds =
             getOrderedDisplayIds(&DisplayTimingData::hwcPresentStartTime);
     DisplayTimeline displayTiming;
+    std::optional<GpuTimeline> firstGpuTimeline;
 
     // Iterate over the displays that use hwc in the same order they are presented
     for (DisplayId displayId : displayIds) {
@@ -459,14 +605,6 @@
 
         displayTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime);
 
-        // If this is the first display, include the duration before hwc present starts
-        if (!previousDisplayTiming.has_value()) {
-            estimatedHwcEndTime += displayTiming.hwcPresentStartTime - mCommitStartTimes[0];
-        } else { // Otherwise add the time since last display's hwc present finished
-            estimatedHwcEndTime +=
-                    displayTiming.hwcPresentStartTime - previousDisplayTiming->hwcPresentEndTime;
-        }
-
         // Update predicted present finish time with this display's present time
         estimatedHwcEndTime = displayTiming.hwcPresentEndTime;
 
@@ -481,6 +619,9 @@
         // Estimate the reference frame's gpu timing
         auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime);
         if (gpuTiming.has_value()) {
+            if (!firstGpuTimeline.has_value()) {
+                firstGpuTimeline = gpuTiming;
+            }
             previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration;
 
             // Estimate the prediction frame's gpu end time from the reference frame
@@ -488,9 +629,7 @@
                                            estimatedGpuEndTime.value_or(TimePoint{0ns})) +
                     gpuTiming->duration;
         }
-        previousDisplayTiming = displayTiming;
     }
-    ATRACE_INT64("Idle duration", idleDuration.ns());
 
     TimePoint estimatedFlingerEndTime = mLastSfPresentEndTime;
 
@@ -503,15 +642,33 @@
     Duration totalDuration = mFrameDelayDuration +
             std::max(estimatedHwcEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) -
             mCommitStartTimes[0];
+    Duration totalDurationWithoutGpu =
+            mFrameDelayDuration + estimatedHwcEndTime - mCommitStartTimes[0];
 
     // We finish SurfaceFlinger when post-composition finishes, so add that in here
     Duration flingerDuration =
             estimatedFlingerEndTime + mLastPostcompDuration - mCommitStartTimes[0];
+    Duration estimatedGpuDuration = firstGpuTimeline.has_value()
+            ? estimatedGpuEndTime.value_or(TimePoint{0ns}) - firstGpuTimeline->startTime
+            : Duration::fromNs(0);
 
     // Combine the two timings into a single normalized one
     Duration combinedDuration = combineTimingEstimates(totalDuration, flingerDuration);
+    Duration cpuDuration = combineTimingEstimates(totalDurationWithoutGpu, flingerDuration);
 
-    return std::make_optional(combinedDuration);
+    WorkDuration duration{
+            .timeStampNanos = TimePoint::now().ns(),
+            .durationNanos = combinedDuration.ns(),
+            .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
+            .cpuDurationNanos = supportsGpuReporting() ? cpuDuration.ns() : 0,
+            .gpuDurationNanos = supportsGpuReporting() ? estimatedGpuDuration.ns() : 0,
+    };
+    if (sTraceHintSessionData) {
+        ATRACE_INT64("Idle duration", idleDuration.ns());
+        ATRACE_INT64("Total duration", totalDuration.ns());
+        ATRACE_INT64("Flinger duration", flingerDuration.ns());
+    }
+    return std::make_optional(duration);
 }
 
 Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration flingerDuration) {
@@ -562,7 +719,7 @@
 
 std::optional<PowerAdvisor::GpuTimeline> PowerAdvisor::DisplayTimingData::estimateGpuTiming(
         std::optional<TimePoint> previousEndTime) {
-    if (!(usedClientComposition && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
+    if (!(requiresRenderEngine && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
         return std::nullopt;
     }
     const TimePoint latestGpuStartTime =
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 0276e44..bc4a41b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -25,9 +25,15 @@
 #include <ui/FenceTime.h>
 #include <utils/Mutex.h>
 
+// FMQ library in IPower does questionable conversions
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
-#include <compositionengine/impl/OutputCompositionState.h>
+#include <fmq/AidlMessageQueue.h>
 #include <powermanager/PowerHalController.h>
+#pragma clang diagnostic pop
+
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <scheduler/Time.h>
 #include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
@@ -54,6 +60,7 @@
     // set before onBootFinished, which gates all methods that run on threads other than SF main
     virtual bool usePowerHintSession() = 0;
     virtual bool supportsPowerHintSession() = 0;
+    virtual bool supportsGpuReporting() = 0;
 
     // Sends a power hint that updates to the target work duration for the frame
     virtual void updateTargetWorkDuration(Duration targetDuration) = 0;
@@ -63,6 +70,8 @@
     virtual void enablePowerHintSession(bool enabled) = 0;
     // Initializes the power hint session
     virtual bool startPowerHintSession(std::vector<int32_t>&& threadIds) = 0;
+    // Provides PowerAdvisor with gpu start time
+    virtual void setGpuStartTime(DisplayId displayId, TimePoint startTime) = 0;
     // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time
     virtual void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) = 0;
     // Reports the start and end times of a hwc validate call this frame for a given display
@@ -75,9 +84,8 @@
     virtual void setExpectedPresentTime(TimePoint expectedPresentTime) = 0;
     // Reports the most recent present fence time and end time once known
     virtual void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) = 0;
-    // Reports whether a display used client composition this frame
-    virtual void setRequiresClientComposition(DisplayId displayId,
-                                              bool requiresClientComposition) = 0;
+    // Reports whether a display requires RenderEngine to draw
+    virtual void setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine) = 0;
     // Reports whether a given display skipped validation this frame
     virtual void setSkippedValidate(DisplayId displayId, bool skipped) = 0;
     // Reports when a hwc present is delayed, and the time that it will resume
@@ -116,17 +124,19 @@
     bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; };
     bool usePowerHintSession() override;
     bool supportsPowerHintSession() override;
+    bool supportsGpuReporting() override;
     void updateTargetWorkDuration(Duration targetDuration) override;
     void reportActualWorkDuration() override;
     void enablePowerHintSession(bool enabled) override;
     bool startPowerHintSession(std::vector<int32_t>&& threadIds) override;
+    void setGpuStartTime(DisplayId displayId, TimePoint startTime) override;
     void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) override;
     void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
                               TimePoint validateEndTime) override;
     void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime,
                              TimePoint presentEndTime) override;
     void setSkippedValidate(DisplayId displayId, bool skipped) override;
-    void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) override;
+    void setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine);
     void setExpectedPresentTime(TimePoint expectedPresentTime) override;
     void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) override;
     void setHwcPresentDelayedTime(DisplayId displayId, TimePoint earliestFrameStartTime) override;
@@ -187,7 +197,7 @@
         std::optional<TimePoint> hwcValidateStartTime;
         std::optional<TimePoint> hwcValidateEndTime;
         std::optional<TimePoint> hwcPresentDelayedTime;
-        bool usedClientComposition = false;
+        bool requiresRenderEngine = false;
         bool skippedValidate = false;
         // Calculate high-level timing milestones from more granular display timing data
         DisplayTimeline calculateDisplayTimeline(TimePoint fenceTime);
@@ -219,15 +229,17 @@
     // Filter and sort the display ids by a given property
     std::vector<DisplayId> getOrderedDisplayIds(
             std::optional<TimePoint> DisplayTimingData::*sortBy);
-    // Estimates a frame's total work duration including gpu time.
-    std::optional<Duration> estimateWorkDuration();
+    // Estimates a frame's total work duration including gpu and gpu time.
+    std::optional<aidl::android::hardware::power::WorkDuration> estimateWorkDuration();
     // There are two different targets and actual work durations we care about,
     // this normalizes them together and takes the max of the two
     Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration);
+    // Whether to use the new "createHintSessionWithConfig" method
+    bool shouldCreateSessionWithConfig() REQUIRES(mHintSessionMutex);
 
     bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex);
+    void setUpFmq() REQUIRES(mHintSessionMutex);
     std::unordered_map<DisplayId, DisplayTimingData> mDisplayTimingData;
-
     // Current frame's delay
     Duration mFrameDelayDuration{0ns};
     // Last frame's post-composition duration
@@ -254,17 +266,25 @@
     std::optional<bool> mSupportsHintSession;
 
     std::mutex mHintSessionMutex;
-    std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mHintSession
-            GUARDED_BY(mHintSessionMutex) = nullptr;
+    std::shared_ptr<power::PowerHintSessionWrapper> mHintSession GUARDED_BY(mHintSessionMutex) =
+            nullptr;
 
     // Initialize to true so we try to call, to check if it's supported
     bool mHasExpensiveRendering = true;
     bool mHasDisplayUpdateImminent = true;
     // Queue of actual durations saved to report
     std::vector<aidl::android::hardware::power::WorkDuration> mHintSessionQueue;
+    std::unique_ptr<::android::AidlMessageQueue<
+            aidl::android::hardware::power::ChannelMessage,
+            ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>>
+            mMsgQueue GUARDED_BY(mHintSessionMutex);
+    std::unique_ptr<::android::AidlMessageQueue<
+            int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>>
+            mFlagQueue GUARDED_BY(mHintSessionMutex);
+    android::hardware::EventFlag* mEventFlag;
+    uint32_t mFmqWriteMask;
     // The latest values we have received for target and actual
     Duration mTargetDuration = kDefaultTargetDuration;
-    std::optional<Duration> mActualDuration;
     // The list of thread ids, stored so we can restart the session from this class if needed
     std::vector<int32_t> mHintSessionThreadIds;
     Duration mLastTargetDurationSent = kDefaultTargetDuration;
@@ -273,6 +293,13 @@
     std::promise<bool> mDelayReportActualMutexAcquisitonPromise;
     bool mTimingTestingMode = false;
 
+    // Hint session configuration data
+    aidl::android::hardware::power::SessionConfig mSessionConfig;
+
+    // Whether createHintSessionWithConfig is supported, assume true until it fails
+    bool mSessionConfigSupported = true;
+    bool mFirstConfigSupportCheck = true;
+
     // Whether we should emit ATRACE_INT data for hint sessions
     static const bool sTraceHintSessionData;
 
@@ -290,6 +317,12 @@
     // How long we expect hwc to run after the present call until it waits for the fence
     static constexpr const Duration kFenceWaitStartDelayValidated{150us};
     static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us};
+
+    void sendHintSessionHint(aidl::android::hardware::power::SessionHint hint);
+
+    template <aidl::android::hardware::power::ChannelMessage::ChannelMessageContents::Tag T,
+              class In>
+    bool writeHintSessionMessage(In* elements, size_t count) REQUIRES(mHintSessionMutex);
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Effects/Daltonizer.cpp b/services/surfaceflinger/Effects/Daltonizer.cpp
index a7090c5..65f2605 100644
--- a/services/surfaceflinger/Effects/Daltonizer.cpp
+++ b/services/surfaceflinger/Effects/Daltonizer.cpp
@@ -37,6 +37,18 @@
     }
 }
 
+void Daltonizer::setLevel(int32_t level) {
+    if (level < 0 || level > 10) {
+        return;
+    }
+
+    float newLevel = level / 10.0f;
+    if (std::fabs(mLevel - newLevel) > 0.09f) {
+        mDirty = true;
+    }
+    mLevel = newLevel;
+}
+
 const mat4& Daltonizer::operator()() {
     if (mDirty) {
         mDirty = false;
@@ -117,25 +129,24 @@
     // a color blind user and "spread" this error onto the healthy cones.
     // The matrices below perform this last step and have been chosen arbitrarily.
 
-    // The amount of correction can be adjusted here.
-
+    // Scale 0 represents no change (mColorTransform is identical matrix).
     // error spread for protanopia
-    const mat4 errp(    1.0, 0.7, 0.7, 0,
-                        0.0, 1.0, 0.0, 0,
-                        0.0, 0.0, 1.0, 0,
-                          0,   0,   0, 1);
+    const mat4 errp(1.0, mLevel, mLevel, 0.0,
+                    0.0,    1.0,    0.0, 0.0,
+                    0.0,    0.0,    1.0, 0.0,
+                    0.0,    0.0,    0.0, 1.0);
 
     // error spread for deuteranopia
-    const mat4 errd(    1.0, 0.0, 0.0, 0,
-                        0.7, 1.0, 0.7, 0,
-                        0.0, 0.0, 1.0, 0,
-                          0,   0,   0, 1);
+    const mat4 errd(   1.0, 0.0,    0.0, 0.0,
+                    mLevel, 1.0, mLevel, 0.0,
+                       0.0, 0.0,    1.0, 0.0,
+                       0.0, 0.0,    0.0, 1.0);
 
     // error spread for tritanopia
-    const mat4 errt(    1.0, 0.0, 0.0, 0,
-                        0.0, 1.0, 0.0, 0,
-                        0.7, 0.7, 1.0, 0,
-                          0,   0,   0, 1);
+    const mat4 errt(   1.0,    0.0, 0.0, 0.0,
+                       0.0,    1.0, 0.0, 0.0,
+                    mLevel, mLevel, 1.0, 0.0,
+                       0.0,    0.0, 0.0, 1.0);
 
     // And the magic happens here...
     // We construct the matrix that will perform the whole correction.
diff --git a/services/surfaceflinger/Effects/Daltonizer.h b/services/surfaceflinger/Effects/Daltonizer.h
index 2fb60e9..f5eaae7 100644
--- a/services/surfaceflinger/Effects/Daltonizer.h
+++ b/services/surfaceflinger/Effects/Daltonizer.h
@@ -21,6 +21,9 @@
 
 namespace android {
 
+// Forward declare test class
+class DaltonizerTest;
+
 enum class ColorBlindnessType {
     None,               // Disables the Daltonizer
     Protanomaly,        // L (red) cone deficient
@@ -37,10 +40,15 @@
 public:
     void setType(ColorBlindnessType type);
     void setMode(ColorBlindnessMode mode);
+    // sets level for correction saturation, [0-10].
+    void setLevel(int32_t level);
 
     // returns the color transform to apply in the shader
     const mat4& operator()();
 
+    // For testing.
+    friend class DaltonizerTest;
+
 private:
     void update();
 
@@ -48,6 +56,8 @@
     ColorBlindnessMode mMode = ColorBlindnessMode::Simulation;
     bool mDirty = true;
     mat4 mColorTransform;
+    // level of error spreading, [0.0-1.0].
+    float mLevel = 0.7f;
 };
 
 } /* namespace android */
diff --git a/services/surfaceflinger/FpsReporter.cpp b/services/surfaceflinger/FpsReporter.cpp
index 155cf4d..a47348f 100644
--- a/services/surfaceflinger/FpsReporter.cpp
+++ b/services/surfaceflinger/FpsReporter.cpp
@@ -26,13 +26,12 @@
 
 namespace android {
 
-FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger,
-                         std::unique_ptr<Clock> clock)
-      : mFrameTimeline(frameTimeline), mFlinger(flinger), mClock(std::move(clock)) {
+FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, std::unique_ptr<Clock> clock)
+      : mFrameTimeline(frameTimeline), mClock(std::move(clock)) {
     LOG_ALWAYS_FATAL_IF(mClock == nullptr, "Passed in null clock when constructing FpsReporter!");
 }
 
-void FpsReporter::dispatchLayerFps() {
+void FpsReporter::dispatchLayerFps(const frontend::LayerHierarchy& layerHierarchy) {
     const auto now = mClock->now();
     if (now - mLastDispatch < kMinDispatchDuration) {
         return;
@@ -52,31 +51,42 @@
     }
 
     std::unordered_set<int32_t> seenTasks;
-    std::vector<std::pair<TrackedListener, sp<Layer>>> listenersAndLayersToReport;
+    std::vector<std::pair<TrackedListener, const frontend::LayerHierarchy*>>
+            listenersAndLayersToReport;
 
-    mFlinger.mCurrentState.traverse([&](Layer* layer) {
-        auto& currentState = layer->getDrawingState();
-        if (currentState.metadata.has(gui::METADATA_TASK_ID)) {
-            int32_t taskId = currentState.metadata.getInt32(gui::METADATA_TASK_ID, 0);
+    layerHierarchy.traverse([&](const frontend::LayerHierarchy& hierarchy,
+                                const frontend::LayerHierarchy::TraversalPath& traversalPath) {
+        if (traversalPath.variant == frontend::LayerHierarchy::Variant::Detached) {
+            return false;
+        }
+        const auto& metadata = hierarchy.getLayer()->metadata;
+        if (metadata.has(gui::METADATA_TASK_ID)) {
+            int32_t taskId = metadata.getInt32(gui::METADATA_TASK_ID, 0);
             if (seenTasks.count(taskId) == 0) {
                 // localListeners is expected to be tiny
                 for (TrackedListener& listener : localListeners) {
                     if (listener.taskId == taskId) {
                         seenTasks.insert(taskId);
-                        listenersAndLayersToReport.push_back(
-                                {listener, sp<Layer>::fromExisting(layer)});
+                        listenersAndLayersToReport.push_back({listener, &hierarchy});
                         break;
                     }
                 }
             }
         }
+        return true;
     });
 
-    for (const auto& [listener, layer] : listenersAndLayersToReport) {
+    for (const auto& [listener, hierarchy] : listenersAndLayersToReport) {
         std::unordered_set<int32_t> layerIds;
 
-        layer->traverse(LayerVector::StateSet::Current,
-                        [&](Layer* layer) { layerIds.insert(layer->getSequence()); });
+        hierarchy->traverse([&](const frontend::LayerHierarchy& hierarchy,
+                                const frontend::LayerHierarchy::TraversalPath& traversalPath) {
+            if (traversalPath.variant == frontend::LayerHierarchy::Variant::Detached) {
+                return false;
+            }
+            layerIds.insert(static_cast<int32_t>(hierarchy.getLayer()->id));
+            return true;
+        });
 
         listener.listener->onFpsReported(mFrameTimeline.computeFps(layerIds));
     }
diff --git a/services/surfaceflinger/FpsReporter.h b/services/surfaceflinger/FpsReporter.h
index 438b1aa..01f1e07 100644
--- a/services/surfaceflinger/FpsReporter.h
+++ b/services/surfaceflinger/FpsReporter.h
@@ -24,6 +24,7 @@
 
 #include "Clock.h"
 #include "FrameTimeline/FrameTimeline.h"
+#include "FrontEnd/LayerHierarchy.h"
 #include "WpHash.h"
 
 namespace android {
@@ -33,13 +34,13 @@
 
 class FpsReporter : public IBinder::DeathRecipient {
 public:
-    FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger,
+    FpsReporter(frametimeline::FrameTimeline& frameTimeline,
                 std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>());
 
     // Dispatches updated layer fps values for the registered listeners
     // This method promotes Layer weak pointers and performs layer stack traversals, so mStateLock
     // must be held when calling this method.
-    void dispatchLayerFps() EXCLUDES(mMutex);
+    void dispatchLayerFps(const frontend::LayerHierarchy&) EXCLUDES(mMutex);
 
     // Override for IBinder::DeathRecipient
     void binderDied(const wp<IBinder>&) override;
@@ -58,7 +59,6 @@
     };
 
     frametimeline::FrameTimeline& mFrameTimeline;
-    SurfaceFlinger& mFlinger;
     static const constexpr std::chrono::steady_clock::duration kMinDispatchDuration =
             std::chrono::milliseconds(500);
     std::unique_ptr<Clock> mClock;
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index 29c9432..8e28cc3 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_library_static {
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 803299c..2596a25 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -390,6 +390,22 @@
     mGpuComposition = true;
 }
 
+// TODO(b/316171339): migrate from perfetto side
+bool SurfaceFrame::isSelfJanky() const {
+    int32_t jankType = getJankType().value_or(JankType::None);
+
+    if (jankType == JankType::None) {
+        return false;
+    }
+
+    int32_t jankBitmask = JankType::AppDeadlineMissed | JankType::Unknown;
+    if (jankType & jankBitmask) {
+        return true;
+    }
+
+    return false;
+}
+
 std::optional<int32_t> SurfaceFrame::getJankType() const {
     std::scoped_lock lock(mMutex);
     if (mPresentState == PresentState::Dropped) {
@@ -527,12 +543,14 @@
 }
 
 void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
-                                      Fps displayFrameRenderRate, nsecs_t& deadlineDelta) {
+                                      Fps displayFrameRenderRate, nsecs_t* outDeadlineDelta) {
     if (mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
         // Cannot do any classification for invalid present time.
         mJankType = JankType::Unknown;
         mJankSeverityType = JankSeverityType::Unknown;
-        deadlineDelta = -1;
+        if (outDeadlineDelta) {
+            *outDeadlineDelta = -1;
+        }
         return;
     }
 
@@ -543,7 +561,9 @@
         mJankType = mPresentState != PresentState::Presented ? JankType::Dropped
                                                              : JankType::AppDeadlineMissed;
         mJankSeverityType = JankSeverityType::Unknown;
-        deadlineDelta = -1;
+        if (outDeadlineDelta) {
+            *outDeadlineDelta = -1;
+        }
         return;
     }
 
@@ -552,11 +572,14 @@
         return;
     }
 
-    deadlineDelta = mActuals.endTime - mPredictions.endTime;
     const nsecs_t presentDelta = mActuals.presentTime - mPredictions.presentTime;
     const nsecs_t deltaToVsync = refreshRate.getPeriodNsecs() > 0
             ? std::abs(presentDelta) % refreshRate.getPeriodNsecs()
             : 0;
+    const nsecs_t deadlineDelta = mActuals.endTime - mPredictions.endTime;
+    if (outDeadlineDelta) {
+        *outDeadlineDelta = deadlineDelta;
+    }
 
     if (deadlineDelta > mJankClassificationThresholds.deadlineThreshold) {
         mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
@@ -655,7 +678,7 @@
     mActuals.presentTime = presentTime;
     nsecs_t deadlineDelta = 0;
 
-    classifyJankLocked(displayFrameJankType, refreshRate, displayFrameRenderRate, deadlineDelta);
+    classifyJankLocked(displayFrameJankType, refreshRate, displayFrameRenderRate, &deadlineDelta);
 
     if (mPredictionState != PredictionState::None) {
         // Only update janky frames if the app used vsync predictions
@@ -665,6 +688,14 @@
     }
 }
 
+void SurfaceFrame::onCommitNotComposited(Fps refreshRate, Fps displayFrameRenderRate) {
+    std::scoped_lock lock(mMutex);
+
+    mDisplayFrameRenderRate = displayFrameRenderRate;
+    mActuals.presentTime = mPredictions.presentTime;
+    classifyJankLocked(JankType::None, refreshRate, displayFrameRenderRate, nullptr);
+}
+
 void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
     int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
 
@@ -896,6 +927,15 @@
     finalizeCurrentDisplayFrame();
 }
 
+void FrameTimeline::onCommitNotComposited() {
+    ATRACE_CALL();
+    std::scoped_lock lock(mMutex);
+    mCurrentDisplayFrame->onCommitNotComposited();
+    mCurrentDisplayFrame.reset();
+    mCurrentDisplayFrame = std::make_shared<DisplayFrame>(mTimeStats, mJankClassificationThresholds,
+                                                          &mTraceCookieCounter);
+}
+
 void FrameTimeline::DisplayFrame::addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame) {
     mSurfaceFrames.push_back(surfaceFrame);
 }
@@ -1078,6 +1118,12 @@
     }
 }
 
+void FrameTimeline::DisplayFrame::onCommitNotComposited() {
+    for (auto& surfaceFrame : mSurfaceFrames) {
+        surfaceFrame->onCommitNotComposited(mRefreshRate, mRenderRate);
+    }
+}
+
 void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid,
                                                    nsecs_t monoBootOffset) const {
     int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
@@ -1113,20 +1159,23 @@
 }
 
 void FrameTimeline::DisplayFrame::addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                                                  nsecs_t previousActualPresentTime) const {
+                                                  nsecs_t previousPredictionPresentTime) const {
     nsecs_t skippedFrameStartTime = 0, skippedFramePresentTime = 0;
     const constexpr float kThresh = 0.5f;
     const constexpr float kRange = 1.5f;
     for (auto& surfaceFrame : mSurfaceFrames) {
-        if (previousActualPresentTime != 0 &&
-            static_cast<float>(mSurfaceFlingerActuals.presentTime - previousActualPresentTime) >=
+        if (previousPredictionPresentTime != 0 &&
+            static_cast<float>(mSurfaceFlingerPredictions.presentTime -
+                               previousPredictionPresentTime) >=
                     static_cast<float>(mRenderRate.getPeriodNsecs()) * kRange &&
             static_cast<float>(surfaceFrame->getPredictions().presentTime) <=
-                    (static_cast<float>(mSurfaceFlingerActuals.presentTime) -
+                    (static_cast<float>(mSurfaceFlingerPredictions.presentTime) -
                      kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
             static_cast<float>(surfaceFrame->getPredictions().presentTime) >=
-                    (static_cast<float>(previousActualPresentTime) -
-                     kThresh * static_cast<float>(mRenderRate.getPeriodNsecs()))) {
+                    (static_cast<float>(previousPredictionPresentTime) +
+                     kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
+            // sf skipped frame is not considered if app is self janked
+            !surfaceFrame->isSelfJanky()) {
             skippedFrameStartTime = surfaceFrame->getPredictions().endTime;
             skippedFramePresentTime = surfaceFrame->getPredictions().presentTime;
             break;
@@ -1215,18 +1264,18 @@
     });
 }
 
-void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                                        nsecs_t previousActualPresentTime) const {
+nsecs_t FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                                           nsecs_t previousPredictionPresentTime) const {
     if (mSurfaceFrames.empty()) {
         // We don't want to trace display frames without any surface frames updates as this cannot
         // be janky
-        return;
+        return previousPredictionPresentTime;
     }
 
     if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
         // DisplayFrame should not have an invalid token.
         ALOGE("Cannot trace DisplayFrame with invalid token");
-        return;
+        return previousPredictionPresentTime;
     }
 
     if (mPredictionState == PredictionState::Valid) {
@@ -1241,8 +1290,9 @@
     }
 
     if (FlagManager::getInstance().add_sf_skipped_frames_to_trace()) {
-        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousActualPresentTime);
+        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousPredictionPresentTime);
     }
+    return mSurfaceFlingerPredictions.presentTime;
 }
 
 float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds) {
@@ -1333,8 +1383,9 @@
         const auto& pendingPresentFence = *mPendingPresentFences.begin();
         const nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
         auto& displayFrame = pendingPresentFence.second;
-        displayFrame->onPresent(signalTime, mPreviousPresentTime);
-        displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, mPreviousPresentTime);
+        displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
+        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                                             mPreviousPredictionPresentTime);
         mPendingPresentFences.erase(mPendingPresentFences.begin());
     }
 
@@ -1349,9 +1400,10 @@
         }
 
         auto& displayFrame = pendingPresentFence.second;
-        displayFrame->onPresent(signalTime, mPreviousPresentTime);
-        displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, mPreviousPresentTime);
-        mPreviousPresentTime = signalTime;
+        displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
+        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                                             mPreviousPredictionPresentTime);
+        mPreviousActualPresentTime = signalTime;
 
         mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
         --i;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index b5047a3..94cfcb4 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -165,6 +165,8 @@
                  TraceCookieCounter* traceCookieCounter, bool isBuffer, GameMode);
     ~SurfaceFrame() = default;
 
+    bool isSelfJanky() const;
+
     // Returns std::nullopt if the frame hasn't been classified yet.
     // Used by both SF and FrameTimeline.
     std::optional<int32_t> getJankType() const;
@@ -202,6 +204,8 @@
     void onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate,
                    Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta,
                    nsecs_t displayPresentDelta);
+    // Sets the frame as none janky as there was no real display frame.
+    void onCommitNotComposited(Fps refreshRate, Fps displayFrameRenderRate);
     // All the timestamps are dumped relative to the baseTime
     void dump(std::string& result, const std::string& indent, nsecs_t baseTime) const;
     // Dumps only the layer, token, is buffer, jank metadata, prediction and present states.
@@ -233,7 +237,7 @@
     void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
     void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
     void classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
-                            Fps displayFrameRenderRate, nsecs_t& deadlineDelta) REQUIRES(mMutex);
+                            Fps displayFrameRenderRate, nsecs_t* outDeadlineDelta) REQUIRES(mMutex);
 
     const int64_t mToken;
     const int32_t mInputEventId;
@@ -316,6 +320,10 @@
     virtual void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                               const std::shared_ptr<FenceTime>& gpuFence) = 0;
 
+    // Tells FrameTimeline that a frame was committed but not composited. This is used to flush
+    // all the associated surface frames.
+    virtual void onCommitNotComposited() = 0;
+
     // Args:
     // -jank : Dumps only the Display Frames that are either janky themselves
     //         or contain janky Surface Frames.
@@ -381,13 +389,15 @@
         // Emits a packet for perfetto tracing. The function body will be executed only if tracing
         // is enabled. monoBootOffset is the difference between SYSTEM_TIME_BOOTTIME
         // and SYSTEM_TIME_MONOTONIC.
-        void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                   nsecs_t previousActualPresentTime) const;
+        nsecs_t trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                      nsecs_t previousPredictionPresentTime) const;
         // Sets the token, vsyncPeriod, predictions and SF start time.
         void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
                         std::optional<TimelineItem> predictions, nsecs_t wakeUpTime);
         // Sets the appropriate metadata and classifies the jank.
         void onPresent(nsecs_t signalTime, nsecs_t previousPresentTime);
+        // Flushes all the surface frames as those were not generating any actual display frames.
+        void onCommitNotComposited();
         // Adds the provided SurfaceFrame to the current display frame.
         void addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame);
 
@@ -473,6 +483,7 @@
     void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate, Fps renderRate) override;
     void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                       const std::shared_ptr<FenceTime>& gpuFence = FenceTime::NO_FENCE) override;
+    void onCommitNotComposited() override;
     void parseArgs(const Vector<String16>& args, std::string& result) override;
     void setMaxDisplayFrames(uint32_t size) override;
     float computeFps(const std::unordered_set<int32_t>& layerIds) override;
@@ -508,7 +519,8 @@
     uint32_t mMaxDisplayFrames;
     std::shared_ptr<TimeStats> mTimeStats;
     const pid_t mSurfaceFlingerPid;
-    nsecs_t mPreviousPresentTime = 0;
+    nsecs_t mPreviousActualPresentTime = 0;
+    nsecs_t mPreviousPredictionPresentTime = 0;
     const JankClassificationThresholds mJankClassificationThresholds;
     static constexpr uint32_t kDefaultMaxDisplayFrames = 64;
     // The initial container size for the vector<SurfaceFrames> inside display frame. Although
diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
index 6502f36..ea51e92 100644
--- a/services/surfaceflinger/FrontEnd/DisplayInfo.h
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -21,6 +21,7 @@
 #include <gui/DisplayInfo.h>
 #include <ui/DisplayMap.h>
 #include <ui/LayerStack.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Transform.h>
 
 namespace android::surfaceflinger::frontend {
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 1e5a6fb..0dcbb3c 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -153,7 +153,7 @@
         out << prefix + (isLastChild ? "└─ " : "├─ ");
         if (variant == LayerHierarchy::Variant::Relative) {
             out << "(Relative) ";
-        } else if (variant == LayerHierarchy::Variant::Mirror) {
+        } else if (LayerHierarchy::isMirror(variant)) {
             if (!includeMirroredHierarchy) {
                 out << "(Mirroring) " << *mLayer << "\n" + prefix + "   └─ ...";
                 return;
@@ -190,8 +190,12 @@
     return outInvalidRelativeRoot != UNASSIGNED_LAYER_ID;
 }
 
-LayerHierarchyBuilder::LayerHierarchyBuilder(
-        const std::vector<std::unique_ptr<RequestedLayerState>>& layers) {
+void LayerHierarchyBuilder::init(const std::vector<std::unique_ptr<RequestedLayerState>>& layers) {
+    mLayerIdToHierarchy.clear();
+    mHierarchies.clear();
+    mRoot = nullptr;
+    mOffscreenRoot = nullptr;
+
     mHierarchies.reserve(layers.size());
     mLayerIdToHierarchy.reserve(layers.size());
     for (auto& layer : layers) {
@@ -202,6 +206,7 @@
         onLayerAdded(layer.get());
     }
     detachHierarchyFromRelativeParent(&mOffscreenRoot);
+    mInitialized = true;
 }
 
 void LayerHierarchyBuilder::attachToParent(LayerHierarchy* hierarchy) {
@@ -284,6 +289,12 @@
         LayerHierarchy* mirror = getHierarchyFromId(mirrorId);
         hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror);
     }
+    if (FlagManager::getInstance().detached_mirror()) {
+        if (layer->layerIdToMirror != UNASSIGNED_LAYER_ID) {
+            LayerHierarchy* mirror = getHierarchyFromId(layer->layerIdToMirror);
+            hierarchy->addChild(mirror, LayerHierarchy::Variant::Detached_Mirror);
+        }
+    }
 }
 
 void LayerHierarchyBuilder::onLayerDestroyed(RequestedLayerState* layer) {
@@ -320,7 +331,7 @@
     LayerHierarchy* hierarchy = getHierarchyFromId(layer->id);
     auto it = hierarchy->mChildren.begin();
     while (it != hierarchy->mChildren.end()) {
-        if (it->second == LayerHierarchy::Variant::Mirror) {
+        if (LayerHierarchy::isMirror(it->second)) {
             it = hierarchy->mChildren.erase(it);
         } else {
             it++;
@@ -330,9 +341,15 @@
     for (uint32_t mirrorId : layer->mirrorIds) {
         hierarchy->addChild(getHierarchyFromId(mirrorId), LayerHierarchy::Variant::Mirror);
     }
+    if (FlagManager::getInstance().detached_mirror()) {
+        if (layer->layerIdToMirror != UNASSIGNED_LAYER_ID) {
+            hierarchy->addChild(getHierarchyFromId(layer->layerIdToMirror),
+                                LayerHierarchy::Variant::Detached_Mirror);
+        }
+    }
 }
 
-void LayerHierarchyBuilder::update(
+void LayerHierarchyBuilder::doUpdate(
         const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
         const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers) {
     // rebuild map
@@ -381,6 +398,32 @@
     attachHierarchyToRelativeParent(&mRoot);
 }
 
+void LayerHierarchyBuilder::update(LayerLifecycleManager& layerLifecycleManager) {
+    if (!mInitialized) {
+        ATRACE_NAME("LayerHierarchyBuilder:init");
+        init(layerLifecycleManager.getLayers());
+    } else if (layerLifecycleManager.getGlobalChanges().test(
+                       RequestedLayerState::Changes::Hierarchy)) {
+        ATRACE_NAME("LayerHierarchyBuilder:update");
+        doUpdate(layerLifecycleManager.getLayers(), layerLifecycleManager.getDestroyedLayers());
+    } else {
+        return; // nothing to do
+    }
+
+    uint32_t invalidRelativeRoot;
+    bool hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot);
+    while (hasRelZLoop) {
+        ATRACE_NAME("FixRelZLoop");
+        TransactionTraceWriter::getInstance().invoke("relz_loop_detected",
+                                                     /*overwrite=*/false);
+        layerLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
+        // reinitialize the hierarchy with the updated layer data
+        init(layerLifecycleManager.getLayers());
+        // check if we have any remaining loops
+        hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot);
+    }
+}
+
 const LayerHierarchy& LayerHierarchyBuilder::getHierarchy() const {
     return mRoot;
 }
@@ -470,7 +513,7 @@
     // stored to reset the id upon destruction.
     traversalPath.id = layerId;
     traversalPath.variant = variant;
-    if (variant == LayerHierarchy::Variant::Mirror) {
+    if (LayerHierarchy::isMirror(variant)) {
         traversalPath.mirrorRootIds.emplace_back(mParentPath.id);
     } else if (variant == LayerHierarchy::Variant::Relative) {
         if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(),
@@ -485,7 +528,7 @@
 LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() {
     // Reset the traversal id to its original parent state using the state that was saved in
     // the constructor.
-    if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) {
+    if (LayerHierarchy::isMirror(mTraversalPath.variant)) {
         mTraversalPath.mirrorRootIds.pop_back();
     } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
         mTraversalPath.relativeRootIds.pop_back();
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index ba2e262..f62e758 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerLifecycleManager.h"
 #include "RequestedLayerState.h"
 #include "ftl/small_vector.h"
 
@@ -34,6 +35,7 @@
 // Detached - child of the parent but currently relative parented to another layer
 // Relative - relative child of the parent
 // Mirror - mirrored from another layer
+// Detached_Mirror - mirrored from another layer, ignoring local transform
 //
 // By representing the hierarchy as a graph, we can represent mirrored layer hierarchies without
 // cloning the layer requested state. The mirrored hierarchy and its corresponding
@@ -42,13 +44,18 @@
 class LayerHierarchy {
 public:
     enum Variant : uint32_t {
-        Attached, // child of the parent
-        Detached, // child of the parent but currently relative parented to another layer
-        Relative, // relative child of the parent
-        Mirror,   // mirrored from another layer
+        Attached,        // child of the parent
+        Detached,        // child of the parent but currently relative parented to another layer
+        Relative,        // relative child of the parent
+        Mirror,          // mirrored from another layer
+        Detached_Mirror, // mirrored from another layer, ignoring local transform
         ftl_first = Attached,
-        ftl_last = Mirror,
+        ftl_last = Detached_Mirror,
     };
+    static inline bool isMirror(Variant variant) {
+        return ((variant == Mirror) || (variant == Detached_Mirror));
+    }
+
     // Represents a unique path to a node.
     // The layer hierarchy is represented as a graph. Each node can be visited by multiple parents.
     // This allows us to represent mirroring in an efficient way. See the example below:
@@ -197,9 +204,8 @@
 // hierarchy from a list of RequestedLayerState and associated change flags.
 class LayerHierarchyBuilder {
 public:
-    LayerHierarchyBuilder(const std::vector<std::unique_ptr<RequestedLayerState>>&);
-    void update(const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
-                const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers);
+    LayerHierarchyBuilder() = default;
+    void update(LayerLifecycleManager& layerLifecycleManager);
     LayerHierarchy getPartialHierarchy(uint32_t, bool childrenOnly) const;
     const LayerHierarchy& getHierarchy() const;
     const LayerHierarchy& getOffscreenHierarchy() const;
@@ -213,14 +219,18 @@
     void detachFromRelativeParent(LayerHierarchy*);
     void attachHierarchyToRelativeParent(LayerHierarchy*);
     void detachHierarchyFromRelativeParent(LayerHierarchy*);
-
+    void init(const std::vector<std::unique_ptr<RequestedLayerState>>&);
+    void doUpdate(const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
+                  const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers);
     void onLayerDestroyed(RequestedLayerState* layer);
     void updateMirrorLayer(RequestedLayerState* layer);
     LayerHierarchy* getHierarchyFromId(uint32_t layerId, bool crashOnFailure = true);
+
     std::unordered_map<uint32_t, LayerHierarchy*> mLayerIdToHierarchy;
     std::vector<std::unique_ptr<LayerHierarchy>> mHierarchies;
     LayerHierarchy mRoot{nullptr};
     LayerHierarchy mOffscreenRoot{nullptr};
+    bool mInitialized = false;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 0983e7c..4b0618e 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -72,8 +72,10 @@
             // Check if we are mirroring a single layer, and if so add it to the list of children
             // to be mirrored.
             layer.layerIdToMirror = linkLayer(layer.layerIdToMirror, layer.id);
-            if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) {
-                layer.mirrorIds.emplace_back(layer.layerIdToMirror);
+            if (!FlagManager::getInstance().detached_mirror()) {
+                if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) {
+                    layer.mirrorIds.emplace_back(layer.layerIdToMirror);
+                }
             }
         }
         layer.touchCropId = linkLayer(layer.touchCropId, layer.id);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 38974a2..70e3c64 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -127,9 +127,8 @@
     pid = state.ownerPid;
     changes = RequestedLayerState::Changes::Created;
     clientChanges = 0;
-    mirrorRootPath = path.variant == LayerHierarchy::Variant::Mirror
-            ? path
-            : LayerHierarchy::TraversalPath::ROOT;
+    mirrorRootPath =
+            LayerHierarchy::isMirror(path.variant) ? path : LayerHierarchy::TraversalPath::ROOT;
     reachablilty = LayerSnapshot::Reachablilty::Unreachable;
     frameRateSelectionPriority = state.frameRateSelectionPriority;
     layerMetadata = state.metadata;
@@ -315,6 +314,7 @@
     if (obj.hasInputInfo()) {
         out << "\n    input{"
             << "(" << obj.inputInfo.inputConfig.string() << ")";
+        if (obj.inputInfo.canOccludePresentation) out << " canOccludePresentation";
         if (obj.touchCropId != UNASSIGNED_LAYER_ID) out << " touchCropId=" << obj.touchCropId;
         if (obj.inputInfo.replaceTouchableRegionWithCrop) out << " replaceTouchableRegionWithCrop";
         auto touchableRegion = obj.inputInfo.touchableRegion.getBounds();
@@ -380,6 +380,9 @@
         currentHdrSdrRatio = requested.currentHdrSdrRatio;
         desiredHdrSdrRatio = requested.desiredHdrSdrRatio;
     }
+    if (forceUpdate || requested.what & layer_state_t::eDesiredHdrHeadroomChanged) {
+        desiredHdrSdrRatio = requested.desiredHdrSdrRatio;
+    }
     if (forceUpdate || requested.what & layer_state_t::eCachingHintChanged) {
         cachingHint = requested.cachingHint;
     }
@@ -468,13 +471,14 @@
         geomContentCrop = requested.getBufferCrop();
     }
 
-    if (forceUpdate ||
-        requested.what &
-                (layer_state_t::eFlagsChanged | layer_state_t::eDestinationFrameChanged |
-                 layer_state_t::ePositionChanged | layer_state_t::eMatrixChanged |
-                 layer_state_t::eBufferTransformChanged |
-                 layer_state_t::eTransformToDisplayInverseChanged) ||
-        requested.changes.test(RequestedLayerState::Changes::BufferSize) || displayChanges) {
+    if ((forceUpdate ||
+         requested.what &
+                 (layer_state_t::eFlagsChanged | layer_state_t::eDestinationFrameChanged |
+                  layer_state_t::ePositionChanged | layer_state_t::eMatrixChanged |
+                  layer_state_t::eBufferTransformChanged |
+                  layer_state_t::eTransformToDisplayInverseChanged) ||
+         requested.changes.test(RequestedLayerState::Changes::BufferSize) || displayChanges) &&
+        !ignoreLocalTransform) {
         localTransform = requested.getTransform(displayRotationFlags);
         localTransformInverse = localTransform.inverse();
     }
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 73ee22f..398e64a 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -80,8 +80,11 @@
     ui::Transform localTransformInverse;
     gui::WindowInfo inputInfo;
     ui::Transform localTransform;
+    // set to true if this snapshot will ignore local transforms. Used when the snapshot
+    // is a mirror root
+    bool ignoreLocalTransform;
     gui::DropInputMode dropInputMode;
-    bool isTrustedOverlay;
+    gui::TrustedOverlay trustedOverlay;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
     scheduler::LayerInfo::FrameRate inheritedFrameRate;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ad5e42b..6d4b0b5 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -22,6 +22,7 @@
 #include <numeric>
 #include <optional>
 
+#include <common/FlagManager.h>
 #include <ftl/small_map.h>
 #include <gui/TraceUtils.h>
 #include <ui/DisplayMap.h>
@@ -358,10 +359,11 @@
     snapshot.relativeLayerMetadata.mMap.clear();
     snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED;
     snapshot.dropInputMode = gui::DropInputMode::NONE;
-    snapshot.isTrustedOverlay = false;
+    snapshot.trustedOverlay = gui::TrustedOverlay::UNSET;
     snapshot.gameMode = gui::GameMode::Unsupported;
     snapshot.frameRate = {};
     snapshot.fixedTransformHint = ui::Transform::ROT_INVALID;
+    snapshot.ignoreLocalTransform = false;
     return snapshot;
 }
 
@@ -575,9 +577,11 @@
     mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, path));
     LayerSnapshot* snapshot = mSnapshots.back().get();
     snapshot->globalZ = static_cast<size_t>(mSnapshots.size()) - 1;
-    if (path.isClone() && path.variant != LayerHierarchy::Variant::Mirror) {
+    if (path.isClone() && !LayerHierarchy::isMirror(path.variant)) {
         snapshot->mirrorRootPath = parentSnapshot.mirrorRootPath;
     }
+    snapshot->ignoreLocalTransform =
+            path.isClone() && path.variant == LayerHierarchy::Variant::Detached_Mirror;
     mPathToSnapshot[path] = snapshot;
 
     mIdToSnapshots.emplace(path.id, snapshot);
@@ -587,8 +591,8 @@
 bool LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) {
     if (!mResortSnapshots && args.forceUpdate == ForceUpdateFlags::NONE &&
         !args.layerLifecycleManager.getGlobalChanges().any(
-                RequestedLayerState::Changes::Hierarchy |
-                RequestedLayerState::Changes::Visibility)) {
+                RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility |
+                RequestedLayerState::Changes::Input)) {
         // We are not force updating and there are no hierarchy or visibility changes. Avoid sorting
         // the snapshots.
         return false;
@@ -732,7 +736,19 @@
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) {
-        snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
+        switch (requested.trustedOverlay) {
+            case gui::TrustedOverlay::UNSET:
+                snapshot.trustedOverlay = parentSnapshot.trustedOverlay;
+                break;
+            case gui::TrustedOverlay::DISABLED:
+                snapshot.trustedOverlay = FlagManager::getInstance().override_trusted_overlay()
+                        ? requested.trustedOverlay
+                        : parentSnapshot.trustedOverlay;
+                break;
+            case gui::TrustedOverlay::ENABLED:
+                snapshot.trustedOverlay = requested.trustedOverlay;
+                break;
+        }
     }
 
     if (snapshot.isHiddenByPolicyFromParent &&
@@ -1028,6 +1044,8 @@
                                        const LayerSnapshot& parentSnapshot,
                                        const LayerHierarchy::TraversalPath& path,
                                        const Args& args) {
+    using InputConfig = gui::WindowInfo::InputConfig;
+
     if (requested.windowInfoHandle) {
         snapshot.inputInfo = *requested.windowInfoHandle->getInfo();
     } else {
@@ -1040,10 +1058,13 @@
     snapshot.touchCropId = requested.touchCropId;
 
     snapshot.inputInfo.id = static_cast<int32_t>(snapshot.uniqueSequence);
-    snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id);
+    snapshot.inputInfo.displayId =
+            ui::LogicalDisplayId{static_cast<int32_t>(snapshot.outputFilter.layerStack.id)};
     snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo()
             ? requested.windowInfoHandle->getInfo()->touchOcclusionMode
             : parentSnapshot.inputInfo.touchOcclusionMode;
+    snapshot.inputInfo.canOccludePresentation = parentSnapshot.inputInfo.canOccludePresentation ||
+            (requested.flags & layer_state_t::eCanOccludePresentation);
     if (requested.dropInputMode == gui::DropInputMode::ALL ||
         parentSnapshot.dropInputMode == gui::DropInputMode::ALL) {
         snapshot.dropInputMode = gui::DropInputMode::ALL;
@@ -1054,6 +1075,11 @@
         snapshot.dropInputMode = gui::DropInputMode::NONE;
     }
 
+    if (snapshot.isSecure ||
+        parentSnapshot.inputInfo.inputConfig.test(InputConfig::SENSITIVE_FOR_PRIVACY)) {
+        snapshot.inputInfo.inputConfig |= InputConfig::SENSITIVE_FOR_PRIVACY;
+    }
+
     updateVisibility(snapshot, snapshot.isVisible);
     if (!needsInputInfo(snapshot, requested)) {
         return;
@@ -1066,14 +1092,14 @@
     auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);
 
     if (!requested.windowInfoHandle) {
-        snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL;
+        snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL;
     }
     fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);
 
     if (noValidDisplay) {
         // Do not let the window receive touches if it is not associated with a valid display
         // transform. We still allow the window to receive keys and prevent ANRs.
-        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE;
+        snapshot.inputInfo.inputConfig |= InputConfig::NOT_TOUCHABLE;
     }
 
     snapshot.inputInfo.alpha = snapshot.color.a;
@@ -1083,7 +1109,7 @@
     // If the window will be blacked out on a display because the display does not have the secure
     // flag and the layer has the secure flag set, then drop input.
     if (!displayInfo.isSecure && snapshot.isSecure) {
-        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+        snapshot.inputInfo.inputConfig |= InputConfig::DROP_INPUT;
     }
 
     if (requested.touchCropId != UNASSIGNED_LAYER_ID || path.isClone()) {
@@ -1099,8 +1125,8 @@
 
     // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
     // if it was set by WM for a known system overlay
-    if (snapshot.isTrustedOverlay) {
-        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY;
+    if (snapshot.trustedOverlay == gui::TrustedOverlay::ENABLED) {
+        snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY;
     }
 
     snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();
@@ -1108,10 +1134,10 @@
     // If the layer is a clone, we need to crop the input region to cloned root to prevent
     // touches from going outside the cloned area.
     if (path.isClone()) {
-        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE;
+        snapshot.inputInfo.inputConfig |= InputConfig::CLONE;
         // Cloned layers shouldn't handle watch outside since their z order is not determined by
         // WM or the client.
-        snapshot.inputInfo.inputConfig.clear(gui::WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH);
+        snapshot.inputInfo.inputConfig.clear(InputConfig::WATCH_OUTSIDE_TOUCH);
     }
 }
 
@@ -1217,8 +1243,8 @@
             Rect inputBoundsInDisplaySpace =
                     getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds,
                                                  displayInfo.transform);
-            snapshot->inputInfo.touchableRegion = snapshot->inputInfo.touchableRegion.intersect(
-                    displayInfo.transform.transform(inputBoundsInDisplaySpace));
+            snapshot->inputInfo.touchableRegion =
+                    snapshot->inputInfo.touchableRegion.intersect(inputBoundsInDisplaySpace);
         }
 
         // If the layer is a clone, we need to crop the input region to cloned root to prevent
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 209df79..139428a 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -98,7 +98,7 @@
     z = 0;
     layerStack = ui::DEFAULT_LAYER_STACK;
     transformToDisplayInverse = false;
-    desiredHdrSdrRatio = 1.f;
+    desiredHdrSdrRatio = -1.f;
     currentHdrSdrRatio = 1.f;
     dataspaceRequested = false;
     hdrMetadata.validTypes = 0;
@@ -118,7 +118,7 @@
     shadowRadius = 0.f;
     fixedTransformHint = ui::Transform::ROT_INVALID;
     destinationFrame.makeInvalid();
-    isTrustedOverlay = false;
+    trustedOverlay = gui::TrustedOverlay::UNSET;
     dropInputMode = gui::DropInputMode::NONE;
     dimmingEnabled = true;
     defaultFrameRateCompatibility = static_cast<int8_t>(scheduler::FrameRateCompatibility::Default);
@@ -163,13 +163,18 @@
     LLOGV(layerId, "requested=%" PRIu64 "flags=%" PRIu64, clientState.what, clientChanges);
 
     if (clientState.what & layer_state_t::eFlagsChanged) {
-        if ((oldFlags ^ flags) & (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque)) {
+        if ((oldFlags ^ flags) &
+            (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque |
+             layer_state_t::eLayerSecure)) {
             changes |= RequestedLayerState::Changes::Visibility |
                     RequestedLayerState::Changes::VisibleRegion;
         }
         if ((oldFlags ^ flags) & layer_state_t::eIgnoreDestinationFrame) {
             changes |= RequestedLayerState::Changes::Geometry;
         }
+        if ((oldFlags ^ flags) & layer_state_t::eCanOccludePresentation) {
+            changes |= RequestedLayerState::Changes::Input;
+        }
     }
 
     if (clientState.what & layer_state_t::eBufferChanged) {
@@ -580,11 +585,13 @@
         return false;
     }
 
-    static constexpr uint64_t deniedFlags = layer_state_t::eProducerDisconnect |
-            layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged |
-            layer_state_t::eTransparentRegionChanged | layer_state_t::eFlagsChanged |
-            layer_state_t::eBlurRegionsChanged | layer_state_t::eLayerStackChanged |
-            layer_state_t::eAutoRefreshChanged | layer_state_t::eReparent;
+    const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged |
+            layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged |
+            layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged |
+            layer_state_t::eLayerStackChanged | layer_state_t::eReparent |
+            (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed()
+                     ? 0
+                     : layer_state_t::eAutoRefreshChanged);
     if (s.what & deniedFlags) {
         ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
                               s.what & deniedFlags);
@@ -603,7 +610,8 @@
             layer_state_t::eShadowRadiusChanged | layer_state_t::eFixedTransformHintChanged |
             layer_state_t::eTrustedOverlayChanged | layer_state_t::eStretchChanged |
             layer_state_t::eBufferCropChanged | layer_state_t::eDestinationFrameChanged |
-            layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged;
+            layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged |
+            layer_state_t::eDesiredHdrHeadroomChanged;
     if (changedFlags & deniedChanges) {
         ATRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__,
                               s.what & deniedChanges);
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 09f33de..48b9640 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -26,6 +26,7 @@
 #include "TransactionState.h"
 
 namespace android::surfaceflinger::frontend {
+using namespace ftl::flag_operators;
 
 // Stores client requested states for a layer.
 // This struct does not store any other states or states pertaining to
@@ -58,6 +59,13 @@
         GameMode = 1u << 19,
         BufferUsageFlags = 1u << 20,
     };
+
+    static constexpr ftl::Flags<Changes> kMustComposite = Changes::Created | Changes::Destroyed |
+            Changes::Hierarchy | Changes::Geometry | Changes::Content | Changes::Input |
+            Changes::Z | Changes::Mirror | Changes::Parent | Changes::RelativeParent |
+            Changes::Metadata | Changes::Visibility | Changes::VisibleRegion | Changes::Buffer |
+            Changes::SidebandStream | Changes::Animation | Changes::BufferSize | Changes::GameMode |
+            Changes::BufferUsageFlags;
     static Rect reduce(const Rect& win, const Region& exclude);
     RequestedLayerState(const LayerCreationArgs&);
     void merge(const ResolvedComposerState&);
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index c8b1059..6b97e2f 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -15,6 +15,7 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
@@ -23,8 +24,6 @@
 #define LOG_TAG "Layer"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include "Layer.h"
-
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/IPCThreadState.h>
@@ -39,7 +38,6 @@
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <gui/BufferItem.h>
-#include <gui/LayerDebugInfo.h>
 #include <gui/Surface.h>
 #include <gui/TraceUtils.h>
 #include <math.h>
@@ -73,21 +71,29 @@
 #include "FrameTracer/FrameTracer.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
+#include "Layer.h"
 #include "LayerProtoHelper.h"
 #include "MutexUtils.h"
 #include "SurfaceFlinger.h"
 #include "TimeStats/TimeStats.h"
+#include "TransactionCallbackInvoker.h"
 #include "TunnelModeEnabledReporter.h"
+#include "Utils/FenceUtils.h"
 
 #define DEBUG_RESIZE 0
 #define EARLY_RELEASE_ENABLED false
 
 namespace android {
+using namespace std::chrono_literals;
 namespace {
 constexpr int kDumpTableRowLength = 159;
 
 const ui::Transform kIdentityTransform;
 
+ui::LogicalDisplayId toLogicalDisplayId(const ui::LayerStack& layerStack) {
+    return ui::LogicalDisplayId{static_cast<int32_t>(layerStack.id)};
+}
+
 bool assignTransform(ui::Transform* dst, ui::Transform& from) {
     if (*dst == from) {
         return false;
@@ -148,8 +154,7 @@
         mWindowType(static_cast<WindowInfo::Type>(
                 args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),
         mLayerCreationFlags(args.flags),
-        mBorderEnabled(false),
-        mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
+        mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName, this)) {
     ALOGV("Creating Layer %s", getDebugName());
 
     uint32_t layerFlags = 0;
@@ -1233,28 +1238,6 @@
     return StretchEffect{};
 }
 
-bool Layer::enableBorder(bool shouldEnable, float width, const half4& color) {
-    if (mBorderEnabled == shouldEnable && mBorderWidth == width && mBorderColor == color) {
-        return false;
-    }
-    mBorderEnabled = shouldEnable;
-    mBorderWidth = width;
-    mBorderColor = color;
-    return true;
-}
-
-bool Layer::isBorderEnabled() {
-    return mBorderEnabled;
-}
-
-float Layer::getBorderWidth() {
-    return mBorderWidth;
-}
-
-const half4& Layer::getBorderColor() {
-    return mBorderColor;
-}
-
 bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
                                            bool* transactionNeeded) {
     // Gets the frame rate to propagate to children.
@@ -1570,10 +1553,6 @@
     return usage;
 }
 
-void Layer::skipReportingTransformHint() {
-    mSkipReportingTransformHint = true;
-}
-
 void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) {
     if (mFlinger->mDebugDisableTransformHint || transformHint & ui::Transform::ROT_INVALID) {
         transformHint = ui::Transform::ROT_0;
@@ -1586,53 +1565,6 @@
 // debugging
 // ----------------------------------------------------------------------------
 
-// TODO(marissaw): add new layer state info to layer debugging
-gui::LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const {
-    using namespace std::string_literals;
-
-    gui::LayerDebugInfo info;
-    const State& ds = getDrawingState();
-    info.mName = getName();
-    sp<Layer> parent = mDrawingParent.promote();
-    info.mParentName = parent ? parent->getName() : "none"s;
-    info.mType = getType();
-
-    info.mVisibleRegion = getVisibleRegion(display);
-    info.mSurfaceDamageRegion = surfaceDamageRegion;
-    info.mLayerStack = getLayerStack().id;
-    info.mX = ds.transform.tx();
-    info.mY = ds.transform.ty();
-    info.mZ = ds.z;
-    info.mCrop = ds.crop;
-    info.mColor = ds.color;
-    info.mFlags = ds.flags;
-    info.mPixelFormat = getPixelFormat();
-    info.mDataSpace = static_cast<android_dataspace>(getDataSpace());
-    info.mMatrix[0][0] = ds.transform[0][0];
-    info.mMatrix[0][1] = ds.transform[0][1];
-    info.mMatrix[1][0] = ds.transform[1][0];
-    info.mMatrix[1][1] = ds.transform[1][1];
-    {
-        sp<const GraphicBuffer> buffer = getBuffer();
-        if (buffer != 0) {
-            info.mActiveBufferWidth = buffer->getWidth();
-            info.mActiveBufferHeight = buffer->getHeight();
-            info.mActiveBufferStride = buffer->getStride();
-            info.mActiveBufferFormat = buffer->format;
-        } else {
-            info.mActiveBufferWidth = 0;
-            info.mActiveBufferHeight = 0;
-            info.mActiveBufferStride = 0;
-            info.mActiveBufferFormat = 0;
-        }
-    }
-    info.mNumQueuedFrames = getQueuedFrameCount();
-    info.mIsOpaque = isOpaque(ds);
-    info.mContentDirty = contentDirty;
-    info.mStretchEffect = getStretchEffect();
-    return info;
-}
-
 void Layer::miniDumpHeader(std::string& result) {
     result.append(kDumpTableRowLength, '-');
     result.append("\n");
@@ -2537,7 +2469,7 @@
         mDrawingState.inputInfo.ownerUid = gui::Uid{mOwnerUid};
         mDrawingState.inputInfo.ownerPid = gui::Pid{mOwnerPid};
         mDrawingState.inputInfo.inputConfig |= WindowInfo::InputConfig::NO_INPUT_CHANNEL;
-        mDrawingState.inputInfo.displayId = getLayerStack().id;
+        mDrawingState.inputInfo.displayId = toLogicalDisplayId(getLayerStack());
     }
 
     const ui::Transform& displayTransform =
@@ -2545,7 +2477,7 @@
 
     WindowInfo info = mDrawingState.inputInfo;
     info.id = sequence;
-    info.displayId = getLayerStack().id;
+    info.displayId = toLogicalDisplayId(getLayerStack());
 
     fillInputFrameInfo(info, displayTransform);
 
@@ -2686,19 +2618,6 @@
     return outputLayer ? outputLayer->getState().visibleRegion : Region();
 }
 
-void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId) {
-    if (mFlinger->mLayerLifecycleManagerEnabled) return;
-    mSnapshot->path.id = clonedFrom->getSequence();
-    mSnapshot->path.mirrorRootIds.emplace_back(mirrorRootId);
-
-    cloneDrawingState(clonedFrom.get());
-    mClonedFrom = clonedFrom;
-    mPremultipliedAlpha = clonedFrom->mPremultipliedAlpha;
-    mPotentialCursor = clonedFrom->mPotentialCursor;
-    mProtectedByApp = clonedFrom->mProtectedByApp;
-    updateCloneBufferInfo();
-}
-
 void Layer::updateCloneBufferInfo() {
     if (!isClone() || !isClonedFromAlive()) {
         return;
@@ -2798,7 +2717,7 @@
         }
         sp<Layer> clonedChild = clonedLayersMap[child];
         if (clonedChild == nullptr) {
-            clonedChild = child->createClone(mirrorRoot->getSequence());
+            clonedChild = child->createClone();
             clonedLayersMap[child] = clonedChild;
         }
         addChildToDrawing(clonedChild);
@@ -2910,8 +2829,7 @@
                               currentMaxAcquiredBufferCount);
 }
 
-void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
-                             ui::LayerStack layerStack) {
+sp<CallbackHandle> Layer::findCallbackHandle() {
     // If we are displayed on multiple displays in a single composition cycle then we would
     // need to do careful tracking to enable the use of the mLastClientCompositionFence.
     //  For example we can only use it if all the displays are client comp, and we need
@@ -2946,11 +2864,75 @@
             break;
         }
     }
+    return ch;
+}
+
+void Layer::prepareReleaseCallbacks(ftl::Future<FenceResult> futureFenceResult,
+                                    ui::LayerStack layerStack) {
+    sp<CallbackHandle> ch = findCallbackHandle();
+
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
         ch->name = mName;
+    } else {
+        // If we didn't get a release callback yet (e.g. some scenarios when capturing
+        // screenshots asynchronously) then make sure we don't drop the fence.
+        // Older fences for the same layer stack can be dropped when a new fence arrives.
+        // An assumption here is that RenderEngine performs work sequentially, so an
+        // incoming fence will not fire before an existing fence.
+        mAdditionalPreviousReleaseFences.emplace_or_replace(layerStack,
+                                                            std::move(futureFenceResult));
     }
+
+    if (mBufferInfo.mBuffer) {
+        mPreviouslyPresentedLayerStacks.push_back(layerStack);
+    }
+
+    if (mDrawingState.frameNumber > 0) {
+        mDrawingState.previousFrameNumber = mDrawingState.frameNumber;
+    }
+}
+
+void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
+                             ui::LayerStack layerStack,
+                             std::function<FenceResult(FenceResult)>&& continuation) {
+    sp<CallbackHandle> ch = findCallbackHandle();
+
+    if (!FlagManager::getInstance().screenshot_fence_preservation() && continuation) {
+        futureFenceResult = ftl::Future(futureFenceResult).then(std::move(continuation)).share();
+    }
+
+    if (ch != nullptr) {
+        ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
+        ch->previousSharedReleaseFences.emplace_back(std::move(futureFenceResult));
+        ch->name = mName;
+    } else if (FlagManager::getInstance().screenshot_fence_preservation()) {
+        // If we didn't get a release callback yet, e.g. some scenarios when capturing screenshots
+        // asynchronously, then make sure we don't drop the fence.
+        mPreviousReleaseFenceAndContinuations.emplace_back(std::move(futureFenceResult),
+                                                           std::move(continuation));
+        std::vector<FenceAndContinuation> mergedFences;
+        sp<Fence> prevFence = nullptr;
+        // For a layer that's frequently screenshotted, try to merge fences to make sure we don't
+        // grow unbounded.
+        for (const auto& futureAndContinuation : mPreviousReleaseFenceAndContinuations) {
+            auto result = futureAndContinuation.future.wait_for(0s);
+            if (result != std::future_status::ready) {
+                mergedFences.emplace_back(futureAndContinuation);
+                continue;
+            }
+
+            mergeFence(getDebugName(),
+                       futureAndContinuation.chain().get().value_or(Fence::NO_FENCE), prevFence);
+        }
+        if (prevFence != nullptr) {
+            mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))).share());
+        }
+
+        mPreviousReleaseFenceAndContinuations.swap(mergedFences);
+    }
+
     if (mBufferInfo.mBuffer) {
         mPreviouslyPresentedLayerStacks.push_back(layerStack);
     }
@@ -2981,13 +2963,7 @@
 
 void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
-        if (mFlinger->mLayerLifecycleManagerEnabled) {
-            handle->transformHint = mTransformHint;
-        } else {
-            handle->transformHint = mSkipReportingTransformHint
-                    ? std::nullopt
-                    : std::make_optional<uint32_t>(mTransformHintLegacy);
-        }
+        handle->transformHint = mTransformHint;
         handle->dequeueReadyTime = dequeueReadyTime;
         handle->currentMaxAcquiredBufferCount =
                 mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
@@ -3246,10 +3222,6 @@
     mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(),
                                       mOwnerUid, postTime, getGameMode());
 
-    if (mFlinger->mLegacyFrontEndEnabled) {
-        recordLayerHistoryBufferUpdate(getLayerProps(), systemTime());
-    }
-
     setFrameTimelineVsyncForBufferTransaction(info, postTime);
 
     if (dequeueTime && *dequeueTime != 0) {
@@ -3362,6 +3334,14 @@
     return true;
 }
 
+bool Layer::setDesiredHdrHeadroom(float desiredRatio) {
+    if (mDrawingState.desiredHdrSdrRatio == desiredRatio) return false;
+    mDrawingState.desiredHdrSdrRatio = desiredRatio;
+    mDrawingState.modified = true;
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
 bool Layer::setCachingHint(gui::CachingHint cachingHint) {
     if (mDrawingState.cachingHint == cachingHint) return false;
     mDrawingState.cachingHint = cachingHint;
@@ -3440,7 +3420,23 @@
             handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence;
             handle->frameNumber = mDrawingState.frameNumber;
             handle->previousFrameNumber = mDrawingState.previousFrameNumber;
-
+            if (FlagManager::getInstance().ce_fence_promise() &&
+                mPreviousReleaseBufferEndpoint == handle->listener) {
+                // Add fence from previous screenshot now so that it can be dispatched to the
+                // client.
+                for (auto& [_, future] : mAdditionalPreviousReleaseFences) {
+                    handle->previousReleaseFences.emplace_back(std::move(future));
+                }
+                mAdditionalPreviousReleaseFences.clear();
+            } else if (FlagManager::getInstance().screenshot_fence_preservation() &&
+                       mPreviousReleaseBufferEndpoint == handle->listener) {
+                // Add fences from previous screenshots now so that they can be dispatched to the
+                // client.
+                for (const auto& futureAndContinution : mPreviousReleaseFenceAndContinuations) {
+                    handle->previousSharedReleaseFences.emplace_back(futureAndContinution.chain());
+                }
+                mPreviousReleaseFenceAndContinuations.clear();
+            }
             // Store so latched time and release fence can be set
             mDrawingState.callbackHandles.push_back(handle);
 
@@ -3687,11 +3683,10 @@
     }
 }
 
-sp<Layer> Layer::createClone(uint32_t mirrorRootId) {
+sp<Layer> Layer::createClone() {
     surfaceflinger::LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0,
                                            LayerMetadata());
     sp<Layer> layer = mFlinger->getFactory().createBufferStateLayer(args);
-    layer->setInitialValuesForClone(sp<Layer>::fromExisting(this), mirrorRootId);
     return layer;
 }
 
@@ -3773,8 +3768,10 @@
     const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged |
             layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged |
             layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged |
-            layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged |
-            layer_state_t::eReparent;
+            layer_state_t::eLayerStackChanged | layer_state_t::eReparent |
+            (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed()
+                     ? 0
+                     : layer_state_t::eAutoRefreshChanged);
 
     if ((s.what & requiredFlags) != requiredFlags) {
         ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
@@ -3905,7 +3902,7 @@
     }
 
     if (s.what & layer_state_t::eTrustedOverlayChanged) {
-        if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) {
+        if (mDrawingState.isTrustedOverlay != (s.trustedOverlay == gui::TrustedOverlay::ENABLED)) {
             ATRACE_FORMAT_INSTANT("%s: false [eTrustedOverlayChanged changed]", __func__);
             return false;
         }
@@ -3949,6 +3946,13 @@
         }
     }
 
+    if (s.what & layer_state_t::eDesiredHdrHeadroomChanged) {
+        if (mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) {
+            ATRACE_FORMAT_INSTANT("%s: false [eDesiredHdrHeadroomChanged changed]", __func__);
+            return false;
+        }
+    }
+
     return true;
 }
 
@@ -3978,7 +3982,7 @@
 }
 
 sp<LayerFE> Layer::copyCompositionEngineLayerFE() const {
-    auto result = mFlinger->getFactory().createLayerFE(mName);
+    auto result = mFlinger->getFactory().createLayerFE(mName, this);
     result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
     return result;
 }
@@ -3990,7 +3994,7 @@
             return layerFE;
         }
     }
-    auto layerFE = mFlinger->getFactory().createLayerFE(mName);
+    auto layerFE = mFlinger->getFactory().createLayerFE(mName, this);
     mLayerFEs.emplace_back(path, layerFE);
     return layerFE;
 }
@@ -4298,7 +4302,6 @@
     if (mTransformHintLegacy == ui::Transform::ROT_INVALID) {
         mTransformHintLegacy = displayTransformHint;
     }
-    mSkipReportingTransformHint = false;
 }
 
 const std::shared_ptr<renderengine::ExternalTexture>& Layer::getExternalTexture() const {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index c772e0e..9db7664 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -18,6 +18,7 @@
 
 #include <android/gui/DropInputMode.h>
 #include <android/gui/ISurfaceComposerClient.h>
+#include <ftl/small_map.h>
 #include <gui/BufferQueue.h>
 #include <gui/LayerState.h>
 #include <gui/WindowInfo.h>
@@ -25,9 +26,11 @@
 #include <math/vec4.h>
 #include <sys/types.h>
 #include <ui/BlurRegion.h>
+#include <ui/DisplayMap.h>
 #include <ui/FloatRect.h>
 #include <ui/FrameStats.h>
 #include <ui/GraphicBuffer.h>
+#include <ui/LayerStack.h>
 #include <ui/PixelFormat.h>
 #include <ui/Region.h>
 #include <ui/StretchEffect.h>
@@ -71,10 +74,6 @@
 struct LayerFECompositionState;
 }
 
-namespace gui {
-class LayerDebugInfo;
-}
-
 namespace frametimeline {
 class SurfaceFrame;
 } // namespace frametimeline
@@ -233,7 +232,7 @@
         bool autoRefresh = false;
         bool dimmingEnabled = true;
         float currentHdrSdrRatio = 1.f;
-        float desiredHdrSdrRatio = 1.f;
+        float desiredHdrSdrRatio = -1.f;
         gui::CachingHint cachingHint = gui::CachingHint::Enabled;
         int64_t latchedVsyncId = 0;
         bool useVsyncIdForRefreshRateSelection = false;
@@ -251,7 +250,7 @@
     // true if this layer is visible, false otherwise
     virtual bool isVisible() const;
 
-    virtual sp<Layer> createClone(uint32_t mirrorRoot);
+    virtual sp<Layer> createClone();
 
     // Set a 2x2 transformation matrix on the layer. This transform
     // will be applied after parent transforms, but before any final
@@ -317,6 +316,7 @@
     void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/);
     bool setDataspace(ui::Dataspace /*dataspace*/);
     bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio);
+    bool setDesiredHdrHeadroom(float desiredRatio);
     bool setCachingHint(gui::CachingHint cachingHint);
     bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
     bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
@@ -546,7 +546,7 @@
         sp<IBinder> mReleaseBufferEndpoint;
 
         bool mFrameLatencyNeeded{false};
-        float mDesiredHdrSdrRatio = 1.f;
+        float mDesiredHdrSdrRatio = -1.f;
     };
 
     BufferInfo mBufferInfo;
@@ -555,7 +555,16 @@
     const compositionengine::LayerFECompositionState* getCompositionState() const;
     bool fenceHasSignaled() const;
     void onPreComposition(nsecs_t refreshStartTime);
-    void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack);
+    void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack,
+                          std::function<FenceResult(FenceResult)>&& continuation = nullptr);
+
+    // Tracks mLastClientCompositionFence and gets the callback handle for this layer.
+    sp<CallbackHandle> findCallbackHandle();
+
+    // Adds the future release fence to a list of fences that are used to release the
+    // last presented buffer. Also keeps track of the layerstack in a list of previous
+    // layerstacks that have been presented.
+    void prepareReleaseCallbacks(ftl::Future<FenceResult>, ui::LayerStack layerStack);
 
     void setWasClientComposed(const sp<Fence>& fence) {
         mLastClientCompositionFence = fence;
@@ -689,12 +698,9 @@
      * Sets display transform hint on BufferLayerConsumer.
      */
     void updateTransformHint(ui::Transform::RotationFlags);
-    void skipReportingTransformHint();
     inline const State& getDrawingState() const { return mDrawingState; }
     inline State& getDrawingState() { return mDrawingState; }
 
-    gui::LayerDebugInfo getLayerDebugInfo(const DisplayDevice*) const;
-
     void miniDumpLegacy(std::string& result, const DisplayDevice&) const;
     void miniDump(std::string& result, const frontend::LayerSnapshot&, const DisplayDevice&) const;
     void dumpFrameStats(std::string& result) const;
@@ -872,10 +878,6 @@
 
     bool setStretchEffect(const StretchEffect& effect);
     StretchEffect getStretchEffect() const;
-    bool enableBorder(bool shouldEnable, float width, const half4& color);
-    bool isBorderEnabled();
-    float getBorderWidth();
-    const half4& getBorderColor();
 
     bool setBufferCrop(const Rect& /* bufferCrop */);
     bool setDestinationFrame(const Rect& /* destinationFrame */);
@@ -932,6 +934,32 @@
     // the release fences from the correct displays when we release the last buffer
     // from the layer.
     std::vector<ui::LayerStack> mPreviouslyPresentedLayerStacks;
+
+    struct FenceAndContinuation {
+        ftl::SharedFuture<FenceResult> future;
+        std::function<FenceResult(FenceResult)> continuation;
+
+        ftl::SharedFuture<FenceResult> chain() const {
+            if (continuation) {
+                return ftl::Future(future).then(continuation).share();
+            } else {
+                return future;
+            }
+        }
+    };
+    std::vector<FenceAndContinuation> mPreviousReleaseFenceAndContinuations;
+
+    // Release fences for buffers that have not yet received a release
+    // callback. A release callback may not be given when capturing
+    // screenshots asynchronously. There may be no buffer update for the
+    // layer, but the layer will still be composited on the screen in every
+    // frame. Kepping track of these fences ensures that they are not dropped
+    // and can be dispatched to the client at a later time. Older fences are
+    // dropped when a layer stack receives a new fence.
+    // TODO(b/300533018): Track fence per multi-instance RenderEngine
+    ftl::SmallMap<ui::LayerStack, ftl::Future<FenceResult>, ui::kDisplayCapacity>
+            mAdditionalPreviousReleaseFences;
+
     // Exposed so SurfaceFlinger can assert that it's held
     const sp<SurfaceFlinger> mFlinger;
 
@@ -948,7 +976,6 @@
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
 
-    virtual void setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId);
     void preparePerFrameCompositionState();
     void preparePerFrameBufferCompositionState();
     void preparePerFrameEffectsCompositionState();
@@ -1223,10 +1250,6 @@
 
     bool findInHierarchy(const sp<Layer>&);
 
-    bool mBorderEnabled = false;
-    float mBorderWidth;
-    half4 mBorderColor;
-
     void setTransformHintLegacy(ui::Transform::RotationFlags);
     void releasePreviousBuffer();
     void resetDrawingStateBufferInfo();
@@ -1234,7 +1257,6 @@
     // Transform hint provided to the producer. This must be accessed holding
     // the mStateLock.
     ui::Transform::RotationFlags mTransformHintLegacy = ui::Transform::ROT_0;
-    bool mSkipReportingTransformHint = true;
     std::optional<ui::Transform::RotationFlags> mTransformHint = std::nullopt;
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 2dbcb84..c2251a8 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -27,6 +27,9 @@
 
 #include "LayerFE.h"
 #include "SurfaceFlinger.h"
+#include "common/FlagManager.h"
+#include "ui/FenceResult.h"
+#include "ui/LayerStack.h"
 
 namespace android {
 
@@ -78,12 +81,21 @@
 
 LayerFE::LayerFE(const std::string& name) : mName(name) {}
 
+LayerFE::~LayerFE() {
+    // Ensures that no promise is left unfulfilled before the LayerFE is destroyed.
+    // An unfulfilled promise could occur when a screenshot is attempted, but the
+    // render area is invalid and there is no memory for the capture result.
+    if (FlagManager::getInstance().ce_fence_promise() &&
+        mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
+        setReleaseFence(Fence::NO_FENCE);
+    }
+}
+
 const compositionengine::LayerFECompositionState* LayerFE::getCompositionState() const {
     return mSnapshot.get();
 }
 
-bool LayerFE::onPreComposition(nsecs_t refreshStartTime, bool) {
-    mCompositionResult.refreshStartTime = refreshStartTime;
+bool LayerFE::onPreComposition(bool) {
     return mSnapshot->hasReadyFrame;
 }
 
@@ -388,4 +400,29 @@
     return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr;
 }
 
+void LayerFE::setReleaseFence(const FenceResult& releaseFence) {
+    // Promises should not be fulfilled more than once. This case can occur if virtual
+    // displays with the same layerstack ID are being created and destroyed in quick
+    // succession, such as in tests. This would result in a race condition in which
+    // multiple displays have the same layerstack ID within the same vsync interval.
+    if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::FULFILLED) {
+        return;
+    }
+    mReleaseFence.set_value(releaseFence);
+    mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::FULFILLED;
+}
+
+// LayerFEs are reused and a new fence needs to be created whevever a buffer is latched.
+ftl::Future<FenceResult> LayerFE::createReleaseFenceFuture() {
+    if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
+        LOG_ALWAYS_FATAL("Attempting to create a new promise while one is still unfulfilled.");
+    }
+    mReleaseFence = std::promise<FenceResult>();
+    mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::INITIALIZED;
+    return mReleaseFence.get_future();
+}
+
+LayerFE::ReleaseFencePromiseStatus LayerFE::getReleaseFencePromiseStatus() {
+    return mReleaseFencePromiseStatus;
+}
 } // namespace android
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index d584fb7..658f949 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -22,13 +22,13 @@
 #include "compositionengine/LayerFE.h"
 #include "compositionengine/LayerFECompositionState.h"
 #include "renderengine/LayerSettings.h"
+#include "ui/LayerStack.h"
+
+#include <ftl/future.h>
 
 namespace android {
 
 struct CompositionResult {
-    // TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition
-    // and remove this field.
-    nsecs_t refreshStartTime = 0;
     std::vector<std::pair<ftl::SharedFuture<FenceResult>, ui::LayerStack>> releaseFences;
     sp<Fence> lastClientCompositionFence = nullptr;
 };
@@ -36,10 +36,11 @@
 class LayerFE : public virtual RefBase, public virtual compositionengine::LayerFE {
 public:
     LayerFE(const std::string& name);
+    virtual ~LayerFE();
 
     // compositionengine::LayerFE overrides
     const compositionengine::LayerFECompositionState* getCompositionState() const override;
-    bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override;
+    bool onPreComposition(bool updatingOutputGeometryThisFrame) override;
     void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack) override;
     const char* getDebugName() const override;
     int32_t getSequence() const override;
@@ -50,6 +51,9 @@
     std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
     CompositionResult&& stealCompositionResult();
+    ftl::Future<FenceResult> createReleaseFenceFuture() override;
+    void setReleaseFence(const FenceResult& releaseFence) override;
+    LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
 
     std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
 
@@ -79,6 +83,8 @@
 
     CompositionResult mCompositionResult;
     std::string mName;
+    std::promise<FenceResult> mReleaseFence;
+    ReleaseFencePromiseStatus mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::UNINITIALIZED;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index aa6026e..496033b 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -334,7 +334,7 @@
                                                                           variant);
         frontend::LayerSnapshot* childSnapshot = getSnapshot(path, layer);
         if (variant == Variant::Attached || variant == Variant::Detached ||
-            variant == Variant::Mirror) {
+            frontend::LayerHierarchy::isMirror(variant)) {
             mChildToParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence;
             layerProto->add_children(childSnapshot->uniqueSequence);
         } else if (variant == Variant::Relative) {
@@ -382,7 +382,8 @@
     layerInfo->set_corner_radius(
             (snapshot.roundedCorner.radius.x + snapshot.roundedCorner.radius.y) / 2.0);
     layerInfo->set_background_blur_radius(snapshot.backgroundBlurRadius);
-    layerInfo->set_is_trusted_overlay(snapshot.isTrustedOverlay);
+    layerInfo->set_is_trusted_overlay(snapshot.trustedOverlay == gui::TrustedOverlay::ENABLED);
+    // TODO(b/339701674) update protos
     LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform());
     LayerProtoHelper::writePositionToProto(transform.tx(), transform.ty(),
                                            [&]() { return layerInfo->mutable_position(); });
@@ -465,7 +466,7 @@
     displays.Reserve(displayInfos.size());
     for (const auto& [layerStack, displayInfo] : displayInfos) {
         auto displayProto = displays.Add();
-        displayProto->set_id(displayInfo.info.displayId);
+        displayProto->set_id(displayInfo.info.displayId.val());
         displayProto->set_layer_stack(layerStack.id);
         displayProto->mutable_size()->set_w(displayInfo.info.logicalWidth);
         displayProto->mutable_size()->set_h(displayInfo.info.logicalHeight);
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 51d4ff8..f323ce7 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -24,31 +24,18 @@
 #include "SurfaceFlinger.h"
 
 namespace android {
-namespace {
 
-void reparentForDrawing(const sp<Layer>& oldParent, const sp<Layer>& newParent,
-                   const Rect& drawingBounds) {
-        // Compute and cache the bounds for the new parent layer.
-        newParent->computeBounds(drawingBounds.toFloatRect(), ui::Transform(),
-            0.f /* shadowRadius */);
-        newParent->updateSnapshot(true /* updateGeometry */);
-        oldParent->setChildrenDrawingParent(newParent);
-};
-
-} // namespace
-
-LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop,
-                                 ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly,
+LayerRenderArea::LayerRenderArea(sp<Layer> layer, frontend::LayerSnapshot layerSnapshot,
+                                 const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
                                  bool allowSecureLayers, const ui::Transform& layerTransform,
                                  const Rect& layerBufferSize, bool hintForSeamlessTransition)
       : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition,
                    allowSecureLayers),
         mLayer(std::move(layer)),
-        mLayerTransform(layerTransform),
+        mLayerSnapshot(std::move(layerSnapshot)),
         mLayerBufferSize(layerBufferSize),
         mCrop(crop),
-        mFlinger(flinger),
-        mChildrenOnly(childrenOnly) {}
+        mTransform(layerTransform) {}
 
 const ui::Transform& LayerRenderArea::getTransform() const {
     return mTransform;
@@ -71,53 +58,4 @@
     }
 }
 
-void LayerRenderArea::render(std::function<void()> drawLayers) {
-    using namespace std::string_literals;
-
-    if (!mChildrenOnly) {
-        mTransform = mLayerTransform.inverse();
-    }
-
-    if (mFlinger.mLayerLifecycleManagerEnabled) {
-        drawLayers();
-        return;
-    }
-    // If layer is offscreen, update mirroring info if it exists
-    if (mLayer->isRemovedFromCurrentState()) {
-        mLayer->traverse(LayerVector::StateSet::Drawing,
-                         [&](Layer* layer) { layer->updateMirrorInfo({}); });
-        mLayer->traverse(LayerVector::StateSet::Drawing,
-                         [&](Layer* layer) { layer->updateCloneBufferInfo(); });
-    }
-
-    if (!mChildrenOnly) {
-        // If the layer is offscreen, compute bounds since we don't compute bounds for offscreen
-        // layers in a regular cycles.
-        if (mLayer->isRemovedFromCurrentState()) {
-            FloatRect maxBounds = mFlinger.getMaxDisplayBounds();
-            mLayer->computeBounds(maxBounds, ui::Transform(), 0.f /* shadowRadius */);
-        }
-        drawLayers();
-    } else {
-        // In the "childrenOnly" case we reparent the children to a screenshot
-        // layer which has no properties set and which does not draw.
-        //  We hold the statelock as the reparent-for-drawing operation modifies the
-        //  hierarchy and there could be readers on Binder threads, like dump.
-        auto screenshotParentLayer = mFlinger.getFactory().createEffectLayer(
-                {&mFlinger, nullptr, "Screenshot Parent"s, ISurfaceComposerClient::eNoColorFill,
-                 LayerMetadata()});
-        {
-            Mutex::Autolock _l(mFlinger.mStateLock);
-            reparentForDrawing(mLayer, screenshotParentLayer, getSourceCrop());
-        }
-        drawLayers();
-        {
-            Mutex::Autolock _l(mFlinger.mStateLock);
-            mLayer->setChildrenDrawingParent(mLayer);
-        }
-    }
-    mLayer->updateSnapshot(/*updateGeometry=*/true);
-    mLayer->updateChildrenSnapshots(/*updateGeometry=*/true);
-}
-
 } // namespace android
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index aa609ee..a12bfca 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -32,8 +32,8 @@
 
 class LayerRenderArea : public RenderArea {
 public:
-    LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop, ui::Size reqSize,
-                    ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers,
+    LayerRenderArea(sp<Layer> layer, frontend::LayerSnapshot layerSnapshot, const Rect& crop,
+                    ui::Size reqSize, ui::Dataspace reqDataSpace, bool allowSecureLayers,
                     const ui::Transform& layerTransform, const Rect& layerBufferSize,
                     bool hintForSeamlessTransition);
 
@@ -42,19 +42,16 @@
     sp<const DisplayDevice> getDisplayDevice() const override;
     Rect getSourceCrop() const override;
 
-    void render(std::function<void()> drawLayers) override;
-    virtual sp<Layer> getParentLayer() const { return mLayer; }
+    sp<Layer> getParentLayer() const override { return mLayer; }
+    const frontend::LayerSnapshot* getLayerSnapshot() const override { return &mLayerSnapshot; }
 
 private:
     const sp<Layer> mLayer;
-    const ui::Transform mLayerTransform;
+    const frontend::LayerSnapshot mLayerSnapshot;
     const Rect mLayerBufferSize;
     const Rect mCrop;
 
     ui::Transform mTransform;
-
-    SurfaceFlinger& mFlinger;
-    const bool mChildrenOnly;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index b960e33..b40f332 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -28,7 +28,7 @@
 
 namespace android {
 
-auto RefreshRateOverlay::draw(int vsyncRate, int renderFps, SkColor color,
+auto RefreshRateOverlay::draw(int refreshRate, int renderFps, SkColor color,
                               ui::Transform::RotationFlags rotation, ftl::Flags<Features> features)
         -> Buffers {
     const size_t loopCount = features.test(Features::Spinner) ? 6 : 1;
@@ -71,7 +71,7 @@
         canvas->setMatrix(canvasTransform);
 
         int left = 0;
-        drawNumber(vsyncRate, left, color, *canvas);
+        drawNumber(refreshRate, left, color, *canvas);
         left += 3 * (kDigitWidth + kDigitSpace);
         if (features.test(Features::Spinner)) {
             switch (i) {
@@ -171,7 +171,7 @@
     return mSurfaceControl != nullptr;
 }
 
-auto RefreshRateOverlay::getOrCreateBuffers(Fps vsyncRate, Fps renderFps) -> const Buffers& {
+auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps) -> const Buffers& {
     static const Buffers kNoBuffers;
     if (!mSurfaceControl) return kNoBuffers;
 
@@ -198,16 +198,16 @@
     createTransaction().setTransform(mSurfaceControl->get(), transform).apply();
 
     BufferCache::const_iterator it =
-            mBufferCache.find({vsyncRate.getIntValue(), renderFps.getIntValue(), transformHint});
+            mBufferCache.find({refreshRate.getIntValue(), renderFps.getIntValue(), transformHint});
     if (it == mBufferCache.end()) {
         // HWC minFps is not known by the framework in order
         // to consider lower rates we set minFps to 0.
         const int minFps = isSetByHwc() ? 0 : mFpsRange.min.getIntValue();
         const int maxFps = mFpsRange.max.getIntValue();
 
-        // Clamp to the range. The current vsyncRate may be outside of this range if the display
+        // Clamp to the range. The current refreshRate may be outside of this range if the display
         // has changed its set of supported refresh rates.
-        const int displayIntFps = std::clamp(vsyncRate.getIntValue(), minFps, maxFps);
+        const int displayIntFps = std::clamp(refreshRate.getIntValue(), minFps, maxFps);
         const int renderIntFps = renderFps.getIntValue();
 
         // Ensure non-zero range to avoid division by zero.
@@ -260,25 +260,26 @@
     createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply();
 }
 
-void RefreshRateOverlay::changeRefreshRate(Fps vsyncRate, Fps renderFps) {
-    mVsyncRate = vsyncRate;
+void RefreshRateOverlay::changeRefreshRate(Fps refreshRate, Fps renderFps) {
+    mRefreshRate = refreshRate;
     mRenderFps = renderFps;
-    const auto buffer = getOrCreateBuffers(vsyncRate, renderFps)[mFrame];
+    const auto buffer = getOrCreateBuffers(refreshRate, renderFps)[mFrame];
     createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
 }
 
 void RefreshRateOverlay::changeRenderRate(Fps renderFps) {
-    if (mFeatures.test(Features::RenderRate) && mVsyncRate && FlagManager::getInstance().misc1()) {
+    if (mFeatures.test(Features::RenderRate) && mRefreshRate &&
+        FlagManager::getInstance().misc1()) {
         mRenderFps = renderFps;
-        const auto buffer = getOrCreateBuffers(*mVsyncRate, renderFps)[mFrame];
+        const auto buffer = getOrCreateBuffers(*mRefreshRate, renderFps)[mFrame];
         createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
     }
 }
 
 void RefreshRateOverlay::animate() {
-    if (!mFeatures.test(Features::Spinner) || !mVsyncRate) return;
+    if (!mFeatures.test(Features::Spinner) || !mRefreshRate) return;
 
-    const auto& buffers = getOrCreateBuffers(*mVsyncRate, *mRenderFps);
+    const auto& buffers = getOrCreateBuffers(*mRefreshRate, *mRenderFps);
     mFrame = (mFrame + 1) % buffers.size();
     const auto buffer = buffers[mFrame];
     createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 0fec470..93ec36e 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -74,12 +74,12 @@
     SurfaceComposerClient::Transaction createTransaction() const;
 
     struct Key {
-        int vsyncRate;
+        int refreshRate;
         int renderFps;
         ui::Transform::RotationFlags flags;
 
         bool operator==(Key other) const {
-            return vsyncRate == other.vsyncRate && renderFps == other.renderFps &&
+            return refreshRate == other.refreshRate && renderFps == other.renderFps &&
                     flags == other.flags;
         }
     };
@@ -87,7 +87,7 @@
     using BufferCache = ftl::SmallMap<Key, Buffers, 9>;
     BufferCache mBufferCache;
 
-    std::optional<Fps> mVsyncRate;
+    std::optional<Fps> mRefreshRate;
     std::optional<Fps> mRenderFps;
     size_t mFrame = 0;
 
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index c888ccc..2b4e234 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -42,6 +42,7 @@
 #include "DisplayRenderArea.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "Layer.h"
+#include "RenderAreaBuilder.h"
 #include "Scheduler/VsyncController.h"
 #include "SurfaceFlinger.h"
 
@@ -278,11 +279,6 @@
     const Rect sampledBounds = sampleRegion.bounds();
     constexpr bool kHintForSeamlessTransition = false;
 
-    SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, sampledBounds, sampledBounds.getSize(),
-                                         ui::Dataspace::V0_SRGB, kHintForSeamlessTransition);
-    });
-
     std::unordered_set<sp<IRegionSamplingListener>, SpHash<IRegionSamplingListener>> listeners;
 
     auto layerFilterFn = [&](const char* layerName, uint32_t layerId, const Rect& bounds,
@@ -377,8 +373,14 @@
     constexpr bool kIsProtected = false;
 
     if (const auto fenceResult =
-                mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer,
-                                             kRegionSampling, kGrayscale, kIsProtected, nullptr)
+                mFlinger.captureScreenshot(SurfaceFlinger::RenderAreaBuilderVariant(
+                                                   std::in_place_type<DisplayRenderAreaBuilder>,
+                                                   sampledBounds, sampledBounds.getSize(),
+                                                   ui::Dataspace::V0_SRGB,
+                                                   kHintForSeamlessTransition,
+                                                   true /* captureSecureLayers */, displayWeak),
+                                           getLayerSnapshots, buffer, kRegionSampling, kGrayscale,
+                                           kIsProtected, nullptr)
                         .get();
         fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 5de148e..e8d20af 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -4,6 +4,8 @@
 #include <ui/Transform.h>
 
 #include <functional>
+
+#include "FrontEnd/LayerSnapshot.h"
 #include "Layer.h"
 
 namespace android {
@@ -47,9 +49,6 @@
 
     virtual ~RenderArea() = default;
 
-    // Invoke drawLayers to render layers into the render area.
-    virtual void render(std::function<void()> drawLayers) { drawLayers(); }
-
     // Returns true if the render area is secure.  A secure layer should be
     // blacked out / skipped when rendered to an insecure render area.
     virtual bool isSecure() const = 0;
@@ -85,6 +84,10 @@
     // capture operation.
     virtual sp<Layer> getParentLayer() const { return nullptr; }
 
+    // If this is a LayerRenderArea, return the layer snapshot
+    // of the root layer of the capture operation
+    virtual const frontend::LayerSnapshot* getLayerSnapshot() const { return nullptr; }
+
     // Returns whether the render result may be used for system animations that
     // must preserve the exact colors of the display.
     bool getHintForSeamlessTransition() const { return mHintForSeamlessTransition; }
diff --git a/services/surfaceflinger/RenderAreaBuilder.h b/services/surfaceflinger/RenderAreaBuilder.h
new file mode 100644
index 0000000..a25c6e0
--- /dev/null
+++ b/services/surfaceflinger/RenderAreaBuilder.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2024 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 "DisplayDevice.h"
+#include "DisplayRenderArea.h"
+#include "LayerRenderArea.h"
+#include "ui/Size.h"
+#include "ui/Transform.h"
+
+namespace android {
+/**
+ * A parameter object for creating a render area
+ */
+struct RenderAreaBuilder {
+    // Source crop of the render area
+    Rect crop;
+
+    // Size of the physical render area
+    ui::Size reqSize;
+
+    // Composition data space of the render area
+    ui::Dataspace reqDataSpace;
+
+    // If true, the secure layer would be blacked out or skipped
+    // when rendered to an insecure render area
+    bool allowSecureLayers;
+
+    // If true, the render result may be used for system animations
+    // that must preserve the exact colors of the display
+    bool hintForSeamlessTransition;
+
+    virtual std::unique_ptr<RenderArea> build() const = 0;
+
+    RenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
+                      bool allowSecureLayers, bool hintForSeamlessTransition)
+          : crop(crop),
+            reqSize(reqSize),
+            reqDataSpace(reqDataSpace),
+            allowSecureLayers(allowSecureLayers),
+            hintForSeamlessTransition(hintForSeamlessTransition) {}
+
+    virtual ~RenderAreaBuilder() = default;
+};
+
+struct DisplayRenderAreaBuilder : RenderAreaBuilder {
+    DisplayRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
+                             bool allowSecureLayers, bool hintForSeamlessTransition,
+                             wp<const DisplayDevice> displayWeak)
+          : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers,
+                              hintForSeamlessTransition),
+            displayWeak(displayWeak) {}
+
+    // Display that render area will be on
+    wp<const DisplayDevice> displayWeak;
+
+    std::unique_ptr<RenderArea> build() const override {
+        return DisplayRenderArea::create(displayWeak, crop, reqSize, reqDataSpace,
+                                         hintForSeamlessTransition, allowSecureLayers);
+    }
+};
+
+struct LayerRenderAreaBuilder : RenderAreaBuilder {
+    LayerRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
+                           bool allowSecureLayers, bool hintForSeamlessTransition, sp<Layer> layer,
+                           bool childrenOnly)
+          : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers,
+                              hintForSeamlessTransition),
+            layer(layer),
+            childrenOnly(childrenOnly) {}
+
+    // Root layer of the render area
+    sp<Layer> layer;
+
+    // Layer snapshot of the root layer
+    frontend::LayerSnapshot layerSnapshot;
+
+    // Transform to be applied on the layers to transform them
+    // into the logical render area
+    ui::Transform layerTransform{ui::Transform()};
+
+    // Buffer bounds
+    Rect layerBufferSize{Rect()};
+
+    // If false, transform is inverted from the parent snapshot
+    bool childrenOnly;
+
+    // Uses parent snapshot to determine layer transform and buffer size
+    void setLayerSnapshot(const frontend::LayerSnapshot& parentSnapshot) {
+        layerSnapshot = parentSnapshot;
+        if (!childrenOnly) {
+            layerTransform = parentSnapshot.localTransform.inverse();
+        }
+        layerBufferSize = parentSnapshot.bufferSize;
+    }
+
+    std::unique_ptr<RenderArea> build() const override {
+        return std::make_unique<LayerRenderArea>(layer, std::move(layerSnapshot), crop, reqSize,
+                                                 reqDataSpace, allowSecureLayers, layerTransform,
+                                                 layerBufferSize, hintForSeamlessTransition);
+    }
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp
index d714848..5455fdc 100644
--- a/services/surfaceflinger/Scheduler/Android.bp
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_defaults {
@@ -52,7 +53,10 @@
 cc_test {
     name: "libscheduler_test",
     test_suites: ["device-tests"],
-    defaults: ["libscheduler_defaults"],
+    defaults: [
+        "libscheduler_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         "tests/FrameTargeterTest.cpp",
         "tests/PresentLatencyTrackerTest.cpp",
@@ -62,9 +66,5 @@
         "libgmock",
         "libgtest",
         "libscheduler",
-        "libsurfaceflingerflags_test",
-    ],
-    shared_libs: [
-        "server_configurable_flags",
     ],
 }
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 693a357..6b65449 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -100,6 +100,11 @@
         case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
             return StringPrintf("ModeChanged{displayId=%s, modeId=%u}",
                                 to_string(event.header.displayId).c_str(), event.modeChange.modeId);
+        case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+            return StringPrintf("HdcpLevelsChange{displayId=%s, connectedLevel=%d, maxLevel=%d}",
+                                to_string(event.header.displayId).c_str(),
+                                event.hdcpLevelsChange.connectedLevel,
+                                event.hdcpLevelsChange.maxLevel);
         default:
             return "Event{}";
     }
@@ -143,7 +148,7 @@
     DisplayEventReceiver::Event event;
     event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE,
                     mode.modePtr->getPhysicalDisplayId(), systemTime()};
-    event.modeChange.modeId = mode.modePtr->getId().value();
+    event.modeChange.modeId = ftl::to_underlying(mode.modePtr->getId());
     event.modeChange.vsyncPeriod = mode.fps.getPeriodNsecs();
     return event;
 }
@@ -170,6 +175,20 @@
             }};
 }
 
+DisplayEventReceiver::Event makeHdcpLevelsChange(PhysicalDisplayId displayId,
+                                                 int32_t connectedLevel, int32_t maxLevel) {
+    return DisplayEventReceiver::Event{
+            .header =
+                    DisplayEventReceiver::Event::Header{
+                            .type = DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE,
+                            .displayId = displayId,
+                            .timestamp = systemTime(),
+                    },
+            .hdcpLevelsChange.connectedLevel = connectedLevel,
+            .hdcpLevelsChange.maxLevel = maxLevel,
+    };
+}
+
 } // namespace
 
 EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
@@ -216,7 +235,8 @@
         ParcelableVsyncEventData* outVsyncEventData) {
     ATRACE_CALL();
     outVsyncEventData->vsync =
-            mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this));
+            mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this),
+                                                  systemTime());
     return binder::Status::ok();
 }
 
@@ -301,7 +321,7 @@
 
     mVsyncRegistration.update({.workDuration = mWorkDuration.get().count(),
                                .readyDuration = mReadyDuration.count(),
-                               .earliestVsync = mLastVsyncCallbackTime.ns()});
+                               .lastVsync = mLastVsyncCallbackTime.ns()});
 }
 
 sp<EventThreadConnection> EventThread::createEventConnection(
@@ -368,8 +388,8 @@
     }
 }
 
-VsyncEventData EventThread::getLatestVsyncEventData(
-        const sp<EventThreadConnection>& connection) const {
+VsyncEventData EventThread::getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                                    nsecs_t now) const {
     // Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
     // way to get vsync data (instead of posting callbacks to Choreographer).
     mCallback.resync();
@@ -380,11 +400,13 @@
     const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
         std::lock_guard<std::mutex> lock(mMutex);
         const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
-                systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
+                now + mWorkDuration.get().count() + mReadyDuration.count());
         return {vsyncTime, vsyncTime - mReadyDuration.count()};
     }();
-    generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
-                          presentTime, deadline);
+    generateFrameTimeline(vsyncEventData, frameInterval.ns(), now, presentTime, deadline);
+    if (FlagManager::getInstance().vrr_config()) {
+        mCallback.onExpectedPresentTimePosted(TimePoint::fromNs(presentTime));
+    }
     return vsyncEventData;
 }
 
@@ -442,6 +464,14 @@
     mCondition.notify_all();
 }
 
+void EventThread::onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                      int32_t maxLevel) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    mPendingEvents.push_back(makeHdcpLevelsChange(displayId, connectedLevel, maxLevel));
+    mCondition.notify_all();
+}
+
 void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
     DisplayEventConsumers consumers;
 
@@ -501,7 +531,7 @@
             const auto scheduleResult =
                     mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
                                                  .readyDuration = mReadyDuration.count(),
-                                                 .earliestVsync = mLastVsyncCallbackTime.ns()});
+                                                 .lastVsync = mLastVsyncCallbackTime.ns()});
             LOG_ALWAYS_FATAL_IF(!scheduleResult, "Error scheduling callback");
         } else {
             mVsyncRegistration.cancel();
@@ -557,6 +587,9 @@
         case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
             return true;
 
+        case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+            return true;
+
         case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: {
             return connection->mEventRegistration.test(
                     gui::ISurfaceComposer::EventRegistration::modeChanged);
@@ -691,6 +724,11 @@
                 removeDisplayEventConnectionLocked(consumer);
         }
     }
+    if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC &&
+        FlagManager::getInstance().vrr_config()) {
+        mCallback.onExpectedPresentTimePosted(
+                TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime()));
+    }
 }
 
 void EventThread::dump(std::string& result) const {
@@ -757,7 +795,7 @@
     if (reschedule) {
         mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
                                      .readyDuration = mReadyDuration.count(),
-                                     .earliestVsync = mLastVsyncCallbackTime.ns()});
+                                     .lastVsync = mLastVsyncCallbackTime.ns()});
     }
     return oldRegistration;
 }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 7842318..f772126 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -127,10 +127,13 @@
     virtual void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) = 0;
     // Requests the next vsync. If resetIdleTimer is set to true, it resets the idle timer.
     virtual void requestNextVsync(const sp<EventThreadConnection>& connection) = 0;
-    virtual VsyncEventData getLatestVsyncEventData(
-            const sp<EventThreadConnection>& connection) const = 0;
+    virtual VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                                   nsecs_t now) const = 0;
 
     virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
+
+    virtual void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                     int32_t maxLevel) = 0;
 };
 
 struct IEventThreadCallback {
@@ -139,6 +142,7 @@
     virtual bool throttleVsync(TimePoint, uid_t) = 0;
     virtual Period getVsyncPeriod(uid_t) = 0;
     virtual void resync() = 0;
+    virtual void onExpectedPresentTimePosted(TimePoint) = 0;
 };
 
 namespace impl {
@@ -156,8 +160,8 @@
     status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
     void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
     void requestNextVsync(const sp<EventThreadConnection>& connection) override;
-    VsyncEventData getLatestVsyncEventData(
-            const sp<EventThreadConnection>& connection) const override;
+    VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                           nsecs_t now) const override;
 
     void enableSyntheticVsync(bool) override;
 
@@ -177,6 +181,9 @@
 
     void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override EXCLUDES(mMutex);
 
+    void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                             int32_t maxLevel) override;
+
 private:
     friend EventThreadTest;
 
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index 3b61de7..43cdb5e 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -30,6 +30,9 @@
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
     virtual void onChoreographerAttached() = 0;
+    virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>,
+                                             Fps renderRate) = 0;
+    virtual void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) = 0;
 
 protected:
     ~ISchedulerCallback() = default;
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 5ce883c..a819b79 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -40,14 +40,15 @@
 
 namespace {
 
-bool isLayerActive(const LayerInfo& info, nsecs_t threshold) {
+bool isLayerActive(const LayerInfo& info, nsecs_t threshold, bool isVrrDevice) {
     if (FlagManager::getInstance().misc1() && !info.isVisible()) {
         return false;
     }
 
     // Layers with an explicit frame rate or frame rate category are kept active,
     // but ignore NoVote.
-    if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) {
+    const auto frameRate = info.getSetFrameRateVote();
+    if (frameRate.isValid() && !frameRate.isNoVote() && frameRate.isVoteValidForMrr(isVrrDevice)) {
         return true;
     }
 
@@ -194,7 +195,7 @@
 
     std::lock_guard lock(mLock);
 
-    partitionLayers(now);
+    partitionLayers(now, selector.isVrrDevice());
 
     for (const auto& [key, value] : mActiveLayerInfos) {
         auto& info = value.second;
@@ -221,9 +222,8 @@
             const std::string categoryString = vote.category == FrameRateCategory::Default
                     ? ""
                     : base::StringPrintf("category=%s", ftl::enum_string(vote.category).c_str());
-            ATRACE_FORMAT_INSTANT("%s %s %s (%d%)", ftl::enum_string(vote.type).c_str(),
-                                  to_string(vote.fps).c_str(), categoryString.c_str(),
-                                  weight * 100);
+            ATRACE_FORMAT_INSTANT("%s %s %s (%.2f)", ftl::enum_string(vote.type).c_str(),
+                                  to_string(vote.fps).c_str(), categoryString.c_str(), weight);
             summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
                                vote.seamlessness, vote.category, vote.categorySmoothSwitchOnly,
                                weight, layerFocused});
@@ -237,7 +237,7 @@
     return summary;
 }
 
-void LayerHistory::partitionLayers(nsecs_t now) {
+void LayerHistory::partitionLayers(nsecs_t now, bool isVrrDevice) {
     ATRACE_CALL();
     const nsecs_t threshold = getActiveLayerThreshold(now);
 
@@ -245,7 +245,7 @@
     LayerInfos::iterator it = mInactiveLayerInfos.begin();
     while (it != mInactiveLayerInfos.end()) {
         auto& [layerUnsafe, info] = it->second;
-        if (isLayerActive(*info, threshold)) {
+        if (isLayerActive(*info, threshold, isVrrDevice)) {
             // move this to the active map
 
             mActiveLayerInfos.insert({it->first, std::move(it->second)});
@@ -263,7 +263,7 @@
     it = mActiveLayerInfos.begin();
     while (it != mActiveLayerInfos.end()) {
         auto& [layerUnsafe, info] = it->second;
-        if (isLayerActive(*info, threshold)) {
+        if (isLayerActive(*info, threshold, isVrrDevice)) {
             // Set layer vote if set
             const auto frameRate = info->getSetFrameRateVote();
 
@@ -280,9 +280,18 @@
                     case Layer::FrameRateCompatibility::Exact:
                         return LayerVoteType::ExplicitExact;
                     case Layer::FrameRateCompatibility::Gte:
-                        return LayerVoteType::ExplicitGte;
+                        if (isVrrDevice) {
+                            return LayerVoteType::ExplicitGte;
+                        } else {
+                            // For MRR, treat GTE votes as Max because it is used for animations and
+                            // scroll. MRR cannot change frame rate without jank, so it should
+                            // prefer smoothness.
+                            return LayerVoteType::Max;
+                        }
                 }
             }();
+            const bool isValuelessVote = voteType == LayerVoteType::NoVote ||
+                    voteType == LayerVoteType::Min || voteType == LayerVoteType::Max;
 
             if (FlagManager::getInstance().game_default_frame_rate()) {
                 // Determine the layer frame rate considering the following priorities:
@@ -301,21 +310,52 @@
 
                 if (gameModeFrameRateOverride.isValid()) {
                     info->setLayerVote({gameFrameRateOverrideVoteType, gameModeFrameRateOverride});
-                } else if (frameRate.isValid()) {
-                    info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate,
+                    ATRACE_FORMAT_INSTANT("GameModeFrameRateOverride");
+                    if (CC_UNLIKELY(mTraceEnabled)) {
+                        trace(*info, gameFrameRateOverrideVoteType,
+                              gameModeFrameRateOverride.getIntValue());
+                    }
+                } else if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
+                    info->setLayerVote({setFrameRateVoteType,
+                                        isValuelessVote ? 0_Hz : frameRate.vote.rate,
                                         frameRate.vote.seamlessness, frameRate.category});
+                    if (CC_UNLIKELY(mTraceEnabled)) {
+                        trace(*info, gameFrameRateOverrideVoteType,
+                              frameRate.vote.rate.getIntValue());
+                    }
                 } else if (gameDefaultFrameRateOverride.isValid()) {
                     info->setLayerVote(
                             {gameFrameRateOverrideVoteType, gameDefaultFrameRateOverride});
+                    ATRACE_FORMAT_INSTANT("GameDefaultFrameRateOverride");
+                    if (CC_UNLIKELY(mTraceEnabled)) {
+                        trace(*info, gameFrameRateOverrideVoteType,
+                              gameDefaultFrameRateOverride.getIntValue());
+                    }
                 } else {
+                    if (frameRate.isValid() && !frameRate.isVoteValidForMrr(isVrrDevice)) {
+                        ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+                                              "%s %s",
+                                              info->getName().c_str(),
+                                              ftl::enum_string(frameRate.vote.type).c_str(),
+                                              to_string(frameRate.vote.rate).c_str(),
+                                              ftl::enum_string(frameRate.category).c_str());
+                    }
                     info->resetLayerVote();
                 }
             } else {
-                if (frameRate.isValid()) {
+                if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
                     const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
-                    info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
-                                        frameRate.category});
+                    info->setLayerVote({type, isValuelessVote ? 0_Hz : frameRate.vote.rate,
+                                        frameRate.vote.seamlessness, frameRate.category});
                 } else {
+                    if (!frameRate.isVoteValidForMrr(isVrrDevice)) {
+                        ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+                                              "%s %s",
+                                              info->getName().c_str(),
+                                              ftl::enum_string(frameRate.vote.type).c_str(),
+                                              to_string(frameRate.vote.rate).c_str(),
+                                              ftl::enum_string(frameRate.category).c_str());
+                    }
                     info->resetLayerVote();
                 }
             }
@@ -342,9 +382,18 @@
 
 std::string LayerHistory::dump() const {
     std::lock_guard lock(mLock);
-    return base::StringPrintf("{size=%zu, active=%zu}",
+    return base::StringPrintf("{size=%zu, active=%zu}\n\tGameFrameRateOverrides=\n\t\t%s",
                               mActiveLayerInfos.size() + mInactiveLayerInfos.size(),
-                              mActiveLayerInfos.size());
+                              mActiveLayerInfos.size(), dumpGameFrameRateOverridesLocked().c_str());
+}
+
+std::string LayerHistory::dumpGameFrameRateOverridesLocked() const {
+    std::string overridesString = "(uid, gameModeOverride, gameDefaultOverride)=";
+    for (auto it = mGameFrameRateOverride.begin(); it != mGameFrameRateOverride.end(); ++it) {
+        base::StringAppendF(&overridesString, "{%u, %d %d} ", it->first,
+                            it->second.first.getIntValue(), it->second.second.getIntValue());
+    }
+    return overridesString;
 }
 
 float LayerHistory::getLayerFramerate(nsecs_t now, int32_t id) const {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 930d06c..c09f148 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -108,10 +108,15 @@
     // keyed by id as returned from Layer::getSequence()
     using LayerInfos = std::unordered_map<int32_t, LayerPair>;
 
+    std::string dumpGameFrameRateOverridesLocked() const REQUIRES(mLock);
+
     // Iterates over layers maps moving all active layers to mActiveLayerInfos and all inactive
-    // layers to mInactiveLayerInfos.
+    // layers to mInactiveLayerInfos. Layer's active state is determined by multiple factors
+    // such as update activity, visibility, and frame rate vote.
     // worst case time complexity is O(2 * inactive + active)
-    void partitionLayers(nsecs_t now) REQUIRES(mLock);
+    // now: the current time (system time) when calling the method
+    // isVrrDevice: true if the device has DisplayMode with VrrConfig specified.
+    void partitionLayers(nsecs_t now, bool isVrrDevice) REQUIRES(mLock);
 
     enum class LayerStatus {
         NotFound,
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 97fca39..632f42a 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -55,14 +55,19 @@
                                    bool pendingModeChange, const LayerProps& props) {
     lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
 
-    mLastUpdatedTime = std::max(lastPresentTime, now);
     *mLayerProps = props;
     switch (updateType) {
         case LayerUpdateType::AnimationTX:
+            mLastUpdatedTime = std::max(lastPresentTime, now);
             mLastAnimationTime = std::max(lastPresentTime, now);
             break;
         case LayerUpdateType::SetFrameRate:
+            if (FlagManager::getInstance().vrr_config()) {
+                break;
+            }
+            FALLTHROUGH_INTENDED;
         case LayerUpdateType::Buffer:
+            mLastUpdatedTime = std::max(lastPresentTime, now);
             FrameTimeData frameTime = {.presentTime = lastPresentTime,
                                        .queueTime = mLastUpdatedTime,
                                        .pendingModeChange = pendingModeChange,
@@ -176,19 +181,19 @@
 bool LayerInfo::hasEnoughDataForHeuristic() const {
     // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
     if (mFrameTimes.size() < 2) {
-        ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
+        ALOGV("%s fewer than 2 frames recorded: %zu", mName.c_str(), mFrameTimes.size());
         return false;
     }
 
     if (!isFrameTimeValid(mFrameTimes.front())) {
-        ALOGV("stale frames still captured");
+        ALOGV("%s stale frames still captured", mName.c_str());
         return false;
     }
 
     const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
     if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
-        ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
-              totalDuration / 1e9f);
+        ALOGV("%s not enough frames captured: %zu | %.2f seconds", mName.c_str(),
+              mFrameTimes.size(), totalDuration / 1e9f);
         return false;
     }
 
@@ -323,7 +328,8 @@
             mLayerVote.type != LayerHistory::LayerVoteType::ExplicitDefault) {
             ATRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
             ALOGV("%s voted %d", mName.c_str(), static_cast<int>(mLayerVote.type));
-            votes.push_back(mLayerVote);
+            votes.push_back({mLayerVote.type, mLayerVote.fps, mLayerVote.seamlessness,
+                             FrameRateCategory::Default, mLayerVote.categorySmoothSwitchOnly});
         }
 
         return votes;
@@ -359,6 +365,8 @@
     }
 
     if (frequent.clearHistory) {
+        ATRACE_FORMAT_INSTANT("frequent.clearHistory");
+        ALOGV("%s frequent.clearHistory", mName.c_str());
         clearHistory(now);
     }
 
@@ -528,6 +536,8 @@
             return FrameRateCategory::Low;
         case ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL:
             return FrameRateCategory::Normal;
+        case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH_HINT:
+            return FrameRateCategory::HighHint;
         case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH:
             return FrameRateCategory::High;
         default:
@@ -555,8 +565,37 @@
     return vote.type == FrameRateCompatibility::NoVote;
 }
 
+bool LayerInfo::FrameRate::isValuelessType() const {
+    // For a valueless frame rate compatibility (type), the frame rate should be unspecified (0 Hz).
+    if (!isApproxEqual(vote.rate, 0_Hz)) {
+        return false;
+    }
+    switch (vote.type) {
+        case FrameRateCompatibility::Min:
+        case FrameRateCompatibility::NoVote:
+            return true;
+        case FrameRateCompatibility::Default:
+        case FrameRateCompatibility::ExactOrMultiple:
+        case FrameRateCompatibility::Exact:
+        case FrameRateCompatibility::Gte:
+            return false;
+    }
+}
+
 bool LayerInfo::FrameRate::isValid() const {
-    return isNoVote() || vote.rate.isValid() || category != FrameRateCategory::Default;
+    return isValuelessType() || vote.rate.isValid() || category != FrameRateCategory::Default;
+}
+
+bool LayerInfo::FrameRate::isVoteValidForMrr(bool isVrrDevice) const {
+    if (isVrrDevice || FlagManager::getInstance().frame_rate_category_mrr()) {
+        return true;
+    }
+
+    if (category == FrameRateCategory::Default) {
+        return true;
+    }
+
+    return false;
 }
 
 std::ostream& operator<<(std::ostream& stream, const LayerInfo::FrameRate& rate) {
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 326e444..a7847bc 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -146,6 +146,13 @@
         // selection.
         bool isNoVote() const;
 
+        // Returns true if the FrameRate has a valid valueless (0 Hz) frame rate type.
+        bool isValuelessType() const;
+
+        // Checks whether the given FrameRate's vote specifications is valid for MRR devices
+        // given the current flagging.
+        bool isVoteValidForMrr(bool isVrrDevice) const;
+
     private:
         static Seamlessness getSeamlessness(Fps rate, Seamlessness seamlessness) {
             if (!rate.isValid()) {
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 18c0a69..ff88d71 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -17,7 +17,6 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <binder/IPCThreadState.h>
-#include <gui/DisplayEventReceiver.h>
 #include <utils/Log.h>
 #include <utils/Timers.h>
 #include <utils/threads.h>
@@ -66,7 +65,7 @@
     {
         std::lock_guard lock(mVsync.mutex);
         mVsync.lastCallbackTime = expectedVsyncTime;
-        mVsync.scheduledFrameTime.reset();
+        mVsync.scheduledFrameTimeOpt.reset();
     }
 
     const auto vsyncId = VsyncId{mVsync.tokenManager->generateTokenForPredictions(
@@ -75,9 +74,9 @@
     mHandler->dispatchFrame(vsyncId, expectedVsyncTime);
 }
 
-void MessageQueue::initVsync(std::shared_ptr<scheduler::VSyncDispatch> dispatch,
-                             frametimeline::TokenManager& tokenManager,
-                             std::chrono::nanoseconds workDuration) {
+void MessageQueue::initVsyncInternal(std::shared_ptr<scheduler::VSyncDispatch> dispatch,
+                                     frametimeline::TokenManager& tokenManager,
+                                     std::chrono::nanoseconds workDuration) {
     std::unique_ptr<scheduler::VSyncCallbackRegistration> oldRegistration;
     {
         std::lock_guard lock(mVsync.mutex);
@@ -87,7 +86,7 @@
     }
 
     // See comments in onNewVsyncSchedule. Today, oldRegistration should be
-    // empty, but nothing prevents us from calling initVsync multiple times, so
+    // empty, but nothing prevents us from calling initVsyncInternal multiple times, so
     // go ahead and destruct it outside the lock for safety.
     oldRegistration.reset();
 }
@@ -122,10 +121,10 @@
                                                             std::placeholders::_3),
                                                   "sf");
     if (reschedule) {
-        mVsync.scheduledFrameTime =
+        mVsync.scheduledFrameTimeOpt =
                 mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
                                                .readyDuration = 0,
-                                               .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                               .lastVsync = mVsync.lastCallbackTime.ns()});
     }
     return oldRegistration;
 }
@@ -140,10 +139,10 @@
     ATRACE_CALL();
     std::lock_guard lock(mVsync.mutex);
     mVsync.workDuration = workDuration;
-    mVsync.scheduledFrameTime =
+    mVsync.scheduledFrameTimeOpt =
             mVsync.registration->update({.workDuration = mVsync.workDuration.get().count(),
                                          .readyDuration = 0,
-                                         .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                         .lastVsync = mVsync.lastCallbackTime.ns()});
 }
 
 void MessageQueue::waitMessage() {
@@ -193,22 +192,20 @@
     ATRACE_CALL();
 
     std::lock_guard lock(mVsync.mutex);
-    mVsync.scheduledFrameTime =
+    mVsync.scheduledFrameTimeOpt =
             mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
                                            .readyDuration = 0,
-                                           .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                           .lastVsync = mVsync.lastCallbackTime.ns()});
 }
 
-auto MessageQueue::getScheduledFrameTime() const -> std::optional<Clock::time_point> {
+std::optional<scheduler::ScheduleResult> MessageQueue::getScheduledFrameResult() const {
     if (mHandler->isFramePending()) {
-        return Clock::now();
+        return scheduler::ScheduleResult{TimePoint::now(), mHandler->getExpectedVsyncTime()};
     }
-
     std::lock_guard lock(mVsync.mutex);
-    if (const auto time = mVsync.scheduledFrameTime) {
-        return Clock::time_point(std::chrono::nanoseconds(*time));
+    if (const auto scheduledFrameTimeline = mVsync.scheduledFrameTimeOpt) {
+        return scheduledFrameTimeline;
     }
-
     return std::nullopt;
 }
 
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index a523147..c5fc371 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -65,8 +65,9 @@
 public:
     virtual ~MessageQueue() = default;
 
-    virtual void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
-                           std::chrono::nanoseconds workDuration) = 0;
+    virtual void initVsyncInternal(std::shared_ptr<scheduler::VSyncDispatch>,
+                                   frametimeline::TokenManager&,
+                                   std::chrono::nanoseconds workDuration) = 0;
     virtual void destroyVsync() = 0;
     virtual void setDuration(std::chrono::nanoseconds workDuration) = 0;
     virtual void waitMessage() = 0;
@@ -75,8 +76,7 @@
     virtual void scheduleConfigure() = 0;
     virtual void scheduleFrame() = 0;
 
-    using Clock = std::chrono::steady_clock;
-    virtual std::optional<Clock::time_point> getScheduledFrameTime() const = 0;
+    virtual std::optional<scheduler::ScheduleResult> getScheduledFrameResult() const = 0;
 };
 
 namespace impl {
@@ -94,7 +94,9 @@
         explicit Handler(MessageQueue& queue) : mQueue(queue) {}
         void handleMessage(const Message& message) override;
 
-        bool isFramePending() const;
+        virtual TimePoint getExpectedVsyncTime() const { return mExpectedVsyncTime.load(); }
+
+        virtual bool isFramePending() const;
 
         virtual void dispatchFrame(VsyncId, TimePoint expectedVsyncTime);
     };
@@ -123,7 +125,7 @@
         TracedOrdinal<std::chrono::nanoseconds> workDuration
                 GUARDED_BY(mutex) = {"VsyncWorkDuration-sf", std::chrono::nanoseconds(0)};
         TimePoint lastCallbackTime GUARDED_BY(mutex);
-        std::optional<nsecs_t> scheduledFrameTime GUARDED_BY(mutex);
+        std::optional<scheduler::ScheduleResult> scheduledFrameTimeOpt GUARDED_BY(mutex);
         TracedOrdinal<int> value = {"VSYNC-sf", 0};
     };
 
@@ -137,8 +139,8 @@
 public:
     explicit MessageQueue(ICompositor&);
 
-    void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
-                   std::chrono::nanoseconds workDuration) override;
+    void initVsyncInternal(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
+                           std::chrono::nanoseconds workDuration) override;
     void destroyVsync() override;
     void setDuration(std::chrono::nanoseconds workDuration) override;
 
@@ -149,7 +151,7 @@
     void scheduleConfigure() override;
     void scheduleFrame() override;
 
-    std::optional<Clock::time_point> getScheduledFrameTime() const override;
+    std::optional<scheduler::ScheduleResult> getScheduledFrameResult() const override;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
index cd45bfd..7e61dc0 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.cpp
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
@@ -115,9 +115,24 @@
             break;
         }
 
-        auto triggerTime = mClock->now() + mInterval;
+        auto triggerTime = mClock->now() + mInterval.load();
         state = TimerState::WAITING;
         while (true) {
+            if (mPaused) {
+                mWaiting = true;
+                int result = sem_wait(&mSemaphore);
+                if (result && errno != EINTR) {
+                    std::stringstream ss;
+                    ss << "sem_wait failed (" << errno << ")";
+                    LOG_ALWAYS_FATAL("%s", ss.str().c_str());
+                }
+
+                mWaiting = false;
+                state = checkForResetAndStop(state);
+                if (state == TimerState::STOPPED) {
+                    break;
+                }
+            }
             // Wait until triggerTime time to check if we need to reset or drop into the idle state.
             if (const auto triggerInterval = triggerTime - mClock->now(); triggerInterval > 0ns) {
                 mWaiting = true;
@@ -137,14 +152,14 @@
                 break;
             }
 
-            if (state == TimerState::WAITING && (triggerTime - mClock->now()) <= 0ns) {
+            if (!mPaused && state == TimerState::WAITING && (triggerTime - mClock->now()) <= 0ns) {
                 triggerTimeout = true;
                 state = TimerState::IDLE;
                 break;
             }
 
             if (state == TimerState::RESET) {
-                triggerTime = mLastResetTime.load() + mInterval;
+                triggerTime = mLastResetTime.load() + mInterval.load();
                 state = TimerState::WAITING;
             }
         }
@@ -179,5 +194,15 @@
     }
 }
 
+void OneShotTimer::pause() {
+    mPaused = true;
+}
+
+void OneShotTimer::resume() {
+    if (mPaused.exchange(false)) {
+        LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed");
+    }
+}
+
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h
index 02e8719..4e1e2ee 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.h
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.h
@@ -43,7 +43,8 @@
                  std::unique_ptr<android::Clock> clock = std::make_unique<SteadyClock>());
     ~OneShotTimer();
 
-    Duration interval() const { return mInterval; }
+    Duration interval() const { return mInterval.load(); }
+    void setInterval(Interval value) { mInterval = value; }
 
     // Initializes and turns on the idle timer.
     void start();
@@ -51,6 +52,10 @@
     void stop();
     // Resets the wakeup time and fires the reset callback.
     void reset();
+    // Pauses the timer. reset calls will get ignored.
+    void pause();
+    // Resumes the timer.
+    void resume();
 
 private:
     // Enum to track in what state is the timer.
@@ -91,7 +96,7 @@
     std::string mName;
 
     // Interval after which timer expires.
-    const Interval mInterval;
+    std::atomic<Interval> mInterval;
 
     // Callback that happens when timer resets.
     const ResetCallback mResetCallback;
@@ -105,6 +110,7 @@
     std::atomic<bool> mResetTriggered = false;
     std::atomic<bool> mStopTriggered = false;
     std::atomic<bool> mWaiting = false;
+    std::atomic<bool> mPaused = false;
     std::atomic<std::chrono::steady_clock::time_point> mLastResetTime;
 };
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index e06221a..2c66492 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -285,10 +285,12 @@
 
 std::string RefreshRateSelector::Policy::toString() const {
     return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s"
-                              ", primaryRanges=%s, appRequestRanges=%s}",
-                              defaultMode.value(), allowGroupSwitching ? "true" : "false",
-                              to_string(primaryRanges).c_str(),
-                              to_string(appRequestRanges).c_str());
+                              ", primaryRanges=%s, appRequestRanges=%s idleScreenConfig=%s}",
+                              ftl::to_underlying(defaultMode),
+                              allowGroupSwitching ? "true" : "false",
+                              to_string(primaryRanges).c_str(), to_string(appRequestRanges).c_str(),
+                              idleScreenConfigOpt ? idleScreenConfigOpt->toString().c_str()
+                                                  : "nullptr");
 }
 
 std::pair<nsecs_t, nsecs_t> RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod,
@@ -420,6 +422,11 @@
     const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
 
     if (layer.vote == LayerVoteType::ExplicitCategory) {
+        // HighHint is considered later for touch boost.
+        if (layer.frameRateCategory == FrameRateCategory::HighHint) {
+            return 0.f;
+        }
+
         if (getFrameRateCategoryRange(layer.frameRateCategory).includes(refreshRate)) {
             return 1.f;
         }
@@ -468,21 +475,23 @@
 }
 
 auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
-                                              GlobalSignals signals) const -> RankedFrameRates {
+                                              GlobalSignals signals, Fps pacesetterFps) const
+        -> RankedFrameRates {
+    GetRankedFrameRatesCache cache{layers, signals, pacesetterFps};
+
     std::lock_guard lock(mLock);
 
-    if (mGetRankedFrameRatesCache &&
-        mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
+    if (mGetRankedFrameRatesCache && mGetRankedFrameRatesCache->matches(cache)) {
         return mGetRankedFrameRatesCache->result;
     }
 
-    const auto result = getRankedFrameRatesLocked(layers, signals);
-    mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
-    return result;
+    cache.result = getRankedFrameRatesLocked(layers, signals, pacesetterFps);
+    mGetRankedFrameRatesCache = std::move(cache);
+    return mGetRankedFrameRatesCache->result;
 }
 
 auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
-                                                    GlobalSignals signals) const
+                                                    GlobalSignals signals, Fps pacesetterFps) const
         -> RankedFrameRates {
     using namespace fps_approx_ops;
     ATRACE_CALL();
@@ -490,6 +499,24 @@
 
     const auto& activeMode = *getActiveModeLocked().modePtr;
 
+    if (pacesetterFps.isValid()) {
+        ALOGV("Follower display");
+
+        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending,
+                                            std::nullopt, [&](FrameRateMode mode) {
+                                                return mode.modePtr->getPeakFps() == pacesetterFps;
+                                            });
+
+        if (!ranking.empty()) {
+            ATRACE_FORMAT_INSTANT("%s (Follower display)",
+                                  to_string(ranking.front().frameRateMode.fps).c_str());
+
+            return {ranking, kNoSignals, pacesetterFps};
+        }
+
+        ALOGW("Follower display cannot follow the pacesetter");
+    }
+
     // Keep the display at max frame rate for the duration of powering on the display.
     if (signals.powerOnImminent) {
         ALOGV("Power On Imminent");
@@ -500,6 +527,8 @@
     }
 
     int noVoteLayers = 0;
+    // Layers that prefer the same mode ("no-op").
+    int noPreferenceLayers = 0;
     int minVoteLayers = 0;
     int maxVoteLayers = 0;
     int explicitDefaultVoteLayers = 0;
@@ -507,6 +536,7 @@
     int explicitExact = 0;
     int explicitGteLayers = 0;
     int explicitCategoryVoteLayers = 0;
+    int interactiveLayers = 0;
     int seamedFocusedLayers = 0;
     int categorySmoothSwitchOnlyLayers = 0;
 
@@ -534,12 +564,15 @@
                 explicitGteLayers++;
                 break;
             case LayerVoteType::ExplicitCategory:
-                explicitCategoryVoteLayers++;
+                if (layer.frameRateCategory == FrameRateCategory::HighHint) {
+                    // HighHint does not count as an explicit signal from an app. It may be
+                    // be a touch signal.
+                    interactiveLayers++;
+                } else {
+                    explicitCategoryVoteLayers++;
+                }
                 if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
-                    // Count this layer for Min vote as well. The explicit vote avoids
-                    // touch boost and idle for choosing a category, while Min vote is for correct
-                    // behavior when all layers are Min or no vote.
-                    minVoteLayers++;
+                    noPreferenceLayers++;
                 }
                 break;
             case LayerVoteType::Heuristic:
@@ -599,6 +632,16 @@
         return {ranking, kNoSignals};
     }
 
+    // If all layers are category NoPreference, use the current config.
+    if (noPreferenceLayers + noVoteLayers == layers.size()) {
+        ALOGV("All layers NoPreference");
+        const auto ascendingWithPreferred =
+                rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
+        ATRACE_FORMAT_INSTANT("%s (All layers NoPreference)",
+                              to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+        return {ascendingWithPreferred, kNoSignals};
+    }
+
     const bool smoothSwitchOnly = categorySmoothSwitchOnlyLayers > 0;
     const DisplayModeId activeModeId = activeMode.getId();
 
@@ -630,6 +673,7 @@
               ftl::enum_string(layer.frameRateCategory).c_str());
         if (layer.isNoVote() || layer.frameRateCategory == FrameRateCategory::NoPreference ||
             layer.vote == LayerVoteType::Min) {
+            ALOGV("%s scoring skipped due to vote", formatLayerInfo(layer, layer.weight).c_str());
             continue;
         }
 
@@ -818,26 +862,37 @@
     // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
     // vote we should not change it if we get a touch event. Only apply touch boost if it will
     // actually increase the refresh rate over the normal selection.
-    const bool touchBoostForExplicitExact = [&] {
+    const auto isTouchBoostForExplicitExact = [&]() -> bool {
         if (supportsAppFrameRateOverrideByContent()) {
             // Enable touch boost if there are other layers besides exact
-            return explicitExact + noVoteLayers != layers.size();
+            return explicitExact + noVoteLayers + explicitGteLayers != layers.size();
         } else {
             // Enable touch boost if there are no exact layers
             return explicitExact == 0;
         }
-    }();
+    };
 
-    const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-    using fps_approx_ops::operator<;
+    const auto isTouchBoostForCategory = [&]() -> bool {
+        return explicitCategoryVoteLayers + noVoteLayers + explicitGteLayers != layers.size();
+    };
 
-    if (signals.touch && explicitDefaultVoteLayers == 0 && explicitCategoryVoteLayers == 0 &&
-        touchBoostForExplicitExact &&
-        scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
-        ALOGV("Touch Boost");
-        ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
-                              to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
-        return {touchRefreshRates, GlobalSignals{.touch = true}};
+    // A method for UI Toolkit to send the touch signal via "HighHint" category vote,
+    // which will touch boost when there are no ExplicitDefault layer votes. This is an
+    // incomplete solution but accounts for cases such as games that use `setFrameRate` with default
+    // compatibility to limit the frame rate, which should not have touch boost.
+    const bool hasInteraction = signals.touch || interactiveLayers > 0;
+
+    if (hasInteraction && explicitDefaultVoteLayers == 0 && isTouchBoostForExplicitExact() &&
+        isTouchBoostForCategory()) {
+        const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+        using fps_approx_ops::operator<;
+
+        if (scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
+            ALOGV("Touch Boost");
+            ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
+                                  to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
+            return {touchRefreshRates, GlobalSignals{.touch = true}};
+        }
     }
 
     // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
@@ -851,8 +906,8 @@
         return {ascendingWithPreferred, kNoSignals};
     }
 
-    ALOGV("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
-    ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
+    ALOGV("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
+    ATRACE_FORMAT_INSTANT("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
     return {ranking, kNoSignals};
 }
 
@@ -930,17 +985,43 @@
     const auto layersByUid = groupLayersByUid(layers);
     UidToFrameRateOverride frameRateOverrides;
     for (const auto& [uid, layersWithSameUid] : layersByUid) {
-        // Layers with ExplicitExactOrMultiple expect touch boost
-        const bool hasExplicitExactOrMultiple =
-                std::any_of(layersWithSameUid.cbegin(), layersWithSameUid.cend(),
-                            [](const auto& layer) {
-                                return layer->vote == LayerVoteType::ExplicitExactOrMultiple;
-                            });
+        // Look for cases that should not have frame rate overrides.
+        bool hasExplicitExactOrMultiple = false;
+        bool hasExplicitDefault = false;
+        bool hasHighHint = false;
+        for (const auto& layer : layersWithSameUid) {
+            switch (layer->vote) {
+                case LayerVoteType::ExplicitExactOrMultiple:
+                    hasExplicitExactOrMultiple = true;
+                    break;
+                case LayerVoteType::ExplicitDefault:
+                    hasExplicitDefault = true;
+                    break;
+                case LayerVoteType::ExplicitCategory:
+                    if (layer->frameRateCategory == FrameRateCategory::HighHint) {
+                        hasHighHint = true;
+                    }
+                    break;
+                default:
+                    // No action
+                    break;
+            }
+            if (hasExplicitExactOrMultiple && hasExplicitDefault && hasHighHint) {
+                break;
+            }
+        }
 
+        // Layers with ExplicitExactOrMultiple expect touch boost
         if (globalSignals.touch && hasExplicitExactOrMultiple) {
             continue;
         }
 
+        // Mirrors getRankedFrameRates. If there is no ExplicitDefault, expect touch boost and
+        // skip frame rate override.
+        if (hasHighHint && !hasExplicitDefault) {
+            continue;
+        }
+
         for (auto& [_, score] : scoredFrameRates) {
             score = 0;
         }
@@ -954,6 +1035,7 @@
             LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
                                         layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
                                         layer->vote != LayerVoteType::ExplicitExact &&
+                                        layer->vote != LayerVoteType::ExplicitGte &&
                                         layer->vote != LayerVoteType::ExplicitCategory,
                                 "Invalid layer vote type for frame rate overrides");
             for (auto& [fps, score] : scoredFrameRates) {
@@ -1167,19 +1249,21 @@
     LOG_ALWAYS_FATAL_IF(!activeModeOpt);
 
     mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())});
+    mIsVrrDevice = FlagManager::getInstance().vrr_config() &&
+            activeModeOpt->get()->getVrrConfig().has_value();
 }
 
 RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId,
                                          Config config)
       : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
-    initializeIdleTimer();
+    initializeIdleTimer(mConfig.legacyIdleTimerTimeout);
     FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId));
 }
 
-void RefreshRateSelector::initializeIdleTimer() {
-    if (mConfig.idleTimerTimeout > 0ms) {
+void RefreshRateSelector::initializeIdleTimer(std::chrono::milliseconds timeout) {
+    if (timeout > 0ms) {
         mIdleTimer.emplace(
-                "IdleTimer", mConfig.idleTimerTimeout,
+                "IdleTimer", timeout,
                 [this] {
                     std::scoped_lock lock(mIdleTimerCallbacksMutex);
                     if (const auto callbacks = getIdleTimerCallbacks()) {
@@ -1302,9 +1386,40 @@
 
         mGetRankedFrameRatesCache.reset();
 
-        if (*getCurrentPolicyLocked() == oldPolicy) {
+        const auto& idleScreenConfigOpt = getCurrentPolicyLocked()->idleScreenConfigOpt;
+        if (idleScreenConfigOpt != oldPolicy.idleScreenConfigOpt) {
+            if (!idleScreenConfigOpt.has_value()) {
+                // fallback to legacy timer if existed, otherwise pause the old timer
+                LOG_ALWAYS_FATAL_IF(!mIdleTimer);
+                if (mConfig.legacyIdleTimerTimeout > 0ms) {
+                    mIdleTimer->setInterval(mConfig.legacyIdleTimerTimeout);
+                    mIdleTimer->resume();
+                } else {
+                    mIdleTimer->pause();
+                }
+            } else if (idleScreenConfigOpt->timeoutMillis > 0) {
+                // create a new timer or reconfigure
+                const auto timeout = std::chrono::milliseconds{idleScreenConfigOpt->timeoutMillis};
+                if (!mIdleTimer) {
+                    initializeIdleTimer(timeout);
+                    if (mIdleTimerStarted) {
+                        mIdleTimer->start();
+                    }
+                } else {
+                    mIdleTimer->setInterval(timeout);
+                    mIdleTimer->resume();
+                }
+            } else {
+                if (mIdleTimer) {
+                    mIdleTimer->pause();
+                }
+            }
+        }
+
+        if (getCurrentPolicyLocked()->similarExceptIdleConfig(oldPolicy)) {
             return SetPolicyResult::Unchanged;
         }
+
         constructAvailableRefreshRates();
 
         displayId = getActiveModeLocked().modePtr->getPhysicalDisplayId();
@@ -1380,7 +1495,8 @@
             }
             return str;
         };
-        ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str());
+        ALOGV("%s render rates: %s, isVrrDevice? %d", rangeName, stringifyModes().c_str(),
+              mIsVrrDevice);
 
         return frameRateModes;
     };
@@ -1389,6 +1505,11 @@
     mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
 }
 
+bool RefreshRateSelector::isVrrDevice() const {
+    std::lock_guard lock(mLock);
+    return mIsVrrDevice;
+}
+
 Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
     using namespace fps_approx_ops;
 
@@ -1469,7 +1590,8 @@
     std::lock_guard lock(mLock);
 
     const auto activeMode = getActiveModeLocked();
-    dumper.dump("activeMode"sv, to_string(activeMode));
+    dumper.dump("renderRate"sv, to_string(activeMode.fps));
+    dumper.dump("activeMode"sv, to_string(*activeMode.modePtr));
 
     dumper.dump("displayModes"sv);
     {
@@ -1500,7 +1622,10 @@
 }
 
 std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() {
-    return mConfig.idleTimerTimeout;
+    if (FlagManager::getInstance().idle_screen_refresh_rate_timeout() && mIdleTimer) {
+        return std::chrono::duration_cast<std::chrono::milliseconds>(mIdleTimer->interval());
+    }
+    return mConfig.legacyIdleTimerTimeout;
 }
 
 // TODO(b/293651105): Extract category FpsRange mapping to OEM-configurable config.
@@ -1509,18 +1634,17 @@
         case FrameRateCategory::High:
             return FpsRange{90_Hz, 120_Hz};
         case FrameRateCategory::Normal:
-            return FpsRange{60_Hz, 90_Hz};
+            return FpsRange{60_Hz, 120_Hz};
         case FrameRateCategory::Low:
-            return FpsRange{30_Hz, 30_Hz};
+            return FpsRange{30_Hz, 120_Hz};
+        case FrameRateCategory::HighHint:
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
             LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s",
                              ftl::enum_string(category).c_str());
-            return FpsRange{0_Hz, 0_Hz};
         default:
             LOG_ALWAYS_FATAL("Invalid frame rate category for range: %s",
                              ftl::enum_string(category).c_str());
-            return FpsRange{0_Hz, 0_Hz};
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index a1a7c28..4f491d9 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -31,7 +31,6 @@
 
 #include "DisplayHardware/DisplayMode.h"
 #include "Scheduler/OneShotTimer.h"
-#include "Scheduler/StrongTyping.h"
 #include "ThreadContext.h"
 #include "Utils/Dumper.h"
 
@@ -68,26 +67,32 @@
         FpsRanges primaryRanges;
         // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details.
         FpsRanges appRequestRanges;
+        // The idle timer configuration, if provided.
+        std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig> idleScreenConfigOpt;
 
         Policy() = default;
 
         Policy(DisplayModeId defaultMode, FpsRange range,
-               bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+               bool allowGroupSwitching = kAllowGroupSwitchingDefault,
+               const std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>&
+                       idleScreenConfigOpt = std::nullopt)
               : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range},
-                       allowGroupSwitching) {}
+                       allowGroupSwitching, idleScreenConfigOpt) {}
 
         Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges,
-               bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+               bool allowGroupSwitching = kAllowGroupSwitchingDefault,
+               const std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>&
+                       idleScreenConfigOpt = std::nullopt)
               : defaultMode(defaultMode),
                 allowGroupSwitching(allowGroupSwitching),
                 primaryRanges(primaryRanges),
-                appRequestRanges(appRequestRanges) {}
+                appRequestRanges(appRequestRanges),
+                idleScreenConfigOpt(idleScreenConfigOpt) {}
 
         bool operator==(const Policy& other) const {
             using namespace fps_approx_ops;
-            return defaultMode == other.defaultMode && primaryRanges == other.primaryRanges &&
-                    appRequestRanges == other.appRequestRanges &&
-                    allowGroupSwitching == other.allowGroupSwitching;
+            return similarExceptIdleConfig(other) &&
+                    idleScreenConfigOpt == other.idleScreenConfigOpt;
         }
 
         bool operator!=(const Policy& other) const { return !(*this == other); }
@@ -96,6 +101,13 @@
             return isApproxEqual(primaryRanges.physical.min, primaryRanges.physical.max);
         }
 
+        bool similarExceptIdleConfig(const Policy& updated) const {
+            using namespace fps_approx_ops;
+            return defaultMode == updated.defaultMode && primaryRanges == updated.primaryRanges &&
+                    appRequestRanges == updated.appRequestRanges &&
+                    allowGroupSwitching == updated.allowGroupSwitching;
+        }
+
         std::string toString() const;
     };
 
@@ -234,14 +246,18 @@
     struct RankedFrameRates {
         FrameRateRanking ranking; // Ordered by descending score.
         GlobalSignals consideredSignals;
+        Fps pacesetterFps;
 
         bool operator==(const RankedFrameRates& other) const {
-            return ranking == other.ranking && consideredSignals == other.consideredSignals;
+            return ranking == other.ranking && consideredSignals == other.consideredSignals &&
+                    isApproxEqual(pacesetterFps, other.pacesetterFps);
         }
     };
 
-    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals) const
-            EXCLUDES(mLock);
+    // If valid, `pacesetterFps` (used by follower displays) filters the ranking to modes matching
+    // that refresh rate.
+    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals,
+                                         Fps pacesetterFps = {}) const EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
@@ -288,7 +304,7 @@
         int frameRateMultipleThreshold = 0;
 
         // The Idle Timer timeout. 0 timeout means no idle timer.
-        std::chrono::milliseconds idleTimerTimeout = 0ms;
+        std::chrono::milliseconds legacyIdleTimerTimeout = 0ms;
 
         // The controller representing how the kernel idle timer will be configured
         // either on the HWC api or sysprop.
@@ -299,7 +315,7 @@
             DisplayModes, DisplayModeId activeModeId,
             Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled,
                              .frameRateMultipleThreshold = 0,
-                             .idleTimerTimeout = 0ms,
+                             .legacyIdleTimerTimeout = 0ms,
                              .kernelIdleTimerController = {}});
 
     RefreshRateSelector(const RefreshRateSelector&) = delete;
@@ -380,12 +396,14 @@
     }
 
     void startIdleTimer() {
+        mIdleTimerStarted = true;
         if (mIdleTimer) {
             mIdleTimer->start();
         }
     }
 
     void stopIdleTimer() {
+        mIdleTimerStarted = false;
         if (mIdleTimer) {
             mIdleTimer->stop();
         }
@@ -407,6 +425,8 @@
 
     std::chrono::milliseconds getIdleTimerTimeout();
 
+    bool isVrrDevice() const;
+
 private:
     friend struct TestableRefreshRateSelector;
 
@@ -416,7 +436,8 @@
     const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);
 
     RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
-                                               GlobalSignals signals) const REQUIRES(mLock);
+                                               GlobalSignals signals, Fps pacesetterFps) const
+            REQUIRES(mLock);
 
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
@@ -475,7 +496,7 @@
     void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock)
             REQUIRES(kMainThreadContext);
 
-    void initializeIdleTimer();
+    void initializeIdleTimer(std::chrono::milliseconds timeout);
 
     std::optional<IdleTimerCallbacks::Callbacks> getIdleTimerCallbacks() const
             REQUIRES(mIdleTimerCallbacksMutex) {
@@ -514,6 +535,9 @@
     std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
     std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
 
+    // Caches whether the device is VRR-compatible based on the active display mode.
+    bool mIsVrrDevice GUARDED_BY(mLock) = false;
+
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
@@ -535,8 +559,16 @@
     Config::FrameRateOverride mFrameRateOverrideConfig;
 
     struct GetRankedFrameRatesCache {
-        std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
+        std::vector<LayerRequirement> layers;
+        GlobalSignals signals;
+        Fps pacesetterFps;
+
         RankedFrameRates result;
+
+        bool matches(const GetRankedFrameRatesCache& other) const {
+            return layers == other.layers && signals == other.signals &&
+                    isApproxEqual(pacesetterFps, other.pacesetterFps);
+        }
     };
     mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
 
@@ -545,6 +577,7 @@
     std::optional<IdleTimerCallbacks> mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex);
     // Used to detect (lack of) frame activity.
     ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
+    std::atomic<bool> mIdleTimerStarted = false;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index 67e1b9c..d51af9a 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -49,10 +49,10 @@
 
 public:
     // TODO(b/185535769): Inject clock to avoid sleeping in tests.
-    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate, PowerMode currentPowerMode)
+    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate)
           : mTimeStats(timeStats),
             mCurrentRefreshRate(currentRefreshRate),
-            mCurrentPowerMode(currentPowerMode) {}
+            mCurrentPowerMode(PowerMode::OFF) {}
 
     void setPowerMode(PowerMode mode) {
         if (mCurrentPowerMode == mode) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index ce59a04..60681a2 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -51,28 +51,25 @@
 #include "FrameRateOverrideMappings.h"
 #include "FrontEnd/LayerHandle.h"
 #include "OneShotTimer.h"
+#include "RefreshRateStats.h"
+#include "SurfaceFlingerFactory.h"
 #include "SurfaceFlingerProperties.h"
+#include "TimeStats/TimeStats.h"
 #include "VSyncTracker.h"
+#include "VsyncConfiguration.h"
 #include "VsyncController.h"
 #include "VsyncSchedule.h"
 
-#define RETURN_IF_INVALID_HANDLE(handle, ...)                        \
-    do {                                                             \
-        if (mConnections.count(handle) == 0) {                       \
-            ALOGE("Invalid connection handle %" PRIuPTR, handle.id); \
-            return __VA_ARGS__;                                      \
-        }                                                            \
-    } while (false)
-
 namespace android::scheduler {
 
 Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features,
-                     sp<VsyncModulator> modulatorPtr, IVsyncTrackerCallback& vsyncTrackerCallback)
-      : impl::MessageQueue(compositor),
+                     surfaceflinger::Factory& factory, Fps activeRefreshRate, TimeStats& timeStats)
+      : android::impl::MessageQueue(compositor),
         mFeatures(features),
-        mVsyncModulator(std::move(modulatorPtr)),
-        mSchedulerCallback(callback),
-        mVsyncTrackerCallback(vsyncTrackerCallback) {}
+        mVsyncConfiguration(factory.createVsyncConfiguration(activeRefreshRate)),
+        mVsyncModulator(sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs())),
+        mRefreshRateStats(std::make_unique<RefreshRateStats>(timeStats, activeRefreshRate)),
+        mSchedulerCallback(callback) {}
 
 Scheduler::~Scheduler() {
     // MessageQueue depends on VsyncSchedule, so first destroy it.
@@ -88,6 +85,11 @@
     demotePacesetterDisplay();
 }
 
+void Scheduler::initVsync(frametimeline::TokenManager& tokenManager,
+                          std::chrono::nanoseconds workDuration) {
+    Impl::initVsyncInternal(getVsyncSchedule()->getDispatch(), tokenManager, workDuration);
+}
+
 void Scheduler::startTimers() {
     using namespace sysprop;
     using namespace std::string_literals;
@@ -122,10 +124,11 @@
 }
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
-    auto schedulePtr = std::make_shared<VsyncSchedule>(
-            selectorPtr->getActiveMode().modePtr, mFeatures,
-            [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); },
-            mVsyncTrackerCallback);
+    auto schedulePtr =
+            std::make_shared<VsyncSchedule>(selectorPtr->getActiveMode().modePtr, mFeatures,
+                                            [this](PhysicalDisplayId id, bool enable) {
+                                                onHardwareVsyncRequest(id, enable);
+                                            });
 
     registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
 }
@@ -151,9 +154,13 @@
     if (isNew) {
         onHardwareVsyncRequest(displayId, false);
     }
+
+    dispatchHotplug(displayId, Hotplug::Connected);
 }
 
 void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+    dispatchHotplug(displayId, Hotplug::Disconnected);
+
     demotePacesetterDisplay();
 
     std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
@@ -182,9 +189,9 @@
     const FrameTargeter::BeginFrameArgs beginFrameArgs =
             {.frameBeginTime = SchedulerClock::now(),
              .vsyncId = vsyncId,
-             // TODO(b/255601557): Calculate per display.
              .expectedVsyncTime = expectedVsyncTime,
-             .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration};
+             .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration,
+             .hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration};
 
     ftl::NonNull<const Display*> pacesetterPtr = pacesetterPtrLocked();
     pacesetterPtr->targeterPtr->beginFrame(beginFrameArgs, *pacesetterPtr->schedulePtr);
@@ -193,15 +200,30 @@
         FrameTargets targets;
         targets.try_emplace(pacesetterPtr->displayId, &pacesetterPtr->targeterPtr->target());
 
+        // TODO (b/256196556): Followers should use the next VSYNC after the frontrunner, not the
+        // pacesetter.
+        // Update expectedVsyncTime, which may have been adjusted by beginFrame.
+        expectedVsyncTime = pacesetterPtr->targeterPtr->target().expectedPresentTime();
+
         for (const auto& [id, display] : mDisplays) {
             if (id == pacesetterPtr->displayId) continue;
 
+            auto followerBeginFrameArgs = beginFrameArgs;
+            followerBeginFrameArgs.expectedVsyncTime =
+                    display.schedulePtr->vsyncDeadlineAfter(expectedVsyncTime);
+
             FrameTargeter& targeter = *display.targeterPtr;
-            targeter.beginFrame(beginFrameArgs, *display.schedulePtr);
+            targeter.beginFrame(followerBeginFrameArgs, *display.schedulePtr);
             targets.try_emplace(id, &targeter.target());
         }
 
-        if (!compositor.commit(pacesetterPtr->displayId, targets)) return;
+        if (!compositor.commit(pacesetterPtr->displayId, targets)) {
+            if (FlagManager::getInstance().vrr_config()) {
+                compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
+            }
+            mSchedulerCallback.onCommitNotComposited(pacesetterPtr->displayId);
+            return;
+        }
     }
 
     // The pacesetter may have changed or been registered anew during commit.
@@ -229,19 +251,10 @@
         mPacesetterFrameDurationFractionToSkip = 0.f;
     }
 
-    if (FlagManager::getInstance().vrr_config()) {
-        const auto minFramePeriod = pacesetterPtr->schedulePtr->minFramePeriod();
-        const auto presentFenceForPastVsync =
-                pacesetterPtr->targeterPtr->target().presentFenceForPastVsync(minFramePeriod);
-        const auto lastConfirmedPresentTime = presentFenceForPastVsync->getSignalTime();
-        if (lastConfirmedPresentTime != Fence::SIGNAL_TIME_PENDING &&
-            lastConfirmedPresentTime != Fence::SIGNAL_TIME_INVALID) {
-            pacesetterPtr->schedulePtr->getTracker()
-                    .onFrameBegin(expectedVsyncTime, TimePoint::fromNs(lastConfirmedPresentTime));
-        }
-    }
-
     const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters);
+    if (FlagManager::getInstance().vrr_config()) {
+        compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
+    }
     compositor.sample();
 
     for (const auto& [id, targeter] : targeters) {
@@ -282,8 +295,11 @@
         const auto pacesetterOpt = pacesetterDisplayLocked();
         LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
         const Display& pacesetter = *pacesetterOpt;
-        return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
-                              pacesetter.schedulePtr->period());
+        const FrameRateMode& frameRateMode = pacesetter.selectorPtr->getActiveMode();
+        const auto refreshRate = frameRateMode.fps;
+        const auto displayVsync = frameRateMode.modePtr->getVsyncRate();
+        const auto numPeriod = RefreshRateSelector::getFrameRateDivisor(displayVsync, refreshRate);
+        return std::make_pair(refreshRate, numPeriod * pacesetter.schedulePtr->period());
     }();
 
     const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
@@ -302,40 +318,40 @@
     // behaviour.
     return Period::fromNs(currentPeriod.ns() * divisor);
 }
+void Scheduler::onExpectedPresentTimePosted(TimePoint expectedPresentTime) {
+    const auto frameRateMode = [this] {
+        std::scoped_lock lock(mDisplayLock);
+        const auto pacesetterOpt = pacesetterDisplayLocked();
+        const Display& pacesetter = *pacesetterOpt;
+        return pacesetter.selectorPtr->getActiveMode();
+    }();
 
-ConnectionHandle Scheduler::createEventThread(Cycle cycle,
-                                              frametimeline::TokenManager* tokenManager,
-                                              std::chrono::nanoseconds workDuration,
-                                              std::chrono::nanoseconds readyDuration) {
-    auto eventThread = std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
-                                                           getVsyncSchedule(), tokenManager, *this,
-                                                           workDuration, readyDuration);
-
-    auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle;
-    handle = createConnection(std::move(eventThread));
-    return handle;
+    if (frameRateMode.modePtr->getVrrConfig()) {
+        mSchedulerCallback.onExpectedPresentTimePosted(expectedPresentTime, frameRateMode.modePtr,
+                                                       frameRateMode.fps);
+    }
 }
 
-ConnectionHandle Scheduler::createConnection(std::unique_ptr<EventThread> eventThread) {
-    const ConnectionHandle handle = ConnectionHandle{mNextConnectionHandleId++};
-    ALOGV("Creating a connection handle with ID %" PRIuPTR, handle.id);
+void Scheduler::createEventThread(Cycle cycle, frametimeline::TokenManager* tokenManager,
+                                  std::chrono::nanoseconds workDuration,
+                                  std::chrono::nanoseconds readyDuration) {
+    auto eventThread =
+            std::make_unique<android::impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
+                                                         getVsyncSchedule(), tokenManager, *this,
+                                                         workDuration, readyDuration);
 
-    auto connection = eventThread->createEventConnection();
-
-    std::lock_guard<std::mutex> lock(mConnectionsLock);
-    mConnections.emplace(handle, Connection{connection, std::move(eventThread)});
-    return handle;
+    if (cycle == Cycle::Render) {
+        mRenderEventThread = std::move(eventThread);
+        mRenderEventConnection = mRenderEventThread->createEventConnection();
+    } else {
+        mLastCompositeEventThread = std::move(eventThread);
+        mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
+    }
 }
 
 sp<IDisplayEventConnection> Scheduler::createDisplayEventConnection(
-        ConnectionHandle handle, EventRegistrationFlags eventRegistration,
-        const sp<IBinder>& layerHandle) {
-    const auto connection = [&]() -> sp<EventThreadConnection> {
-        std::scoped_lock lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle, nullptr);
-
-        return mConnections[handle].thread->createEventConnection(eventRegistration);
-    }();
+        Cycle cycle, EventRegistrationFlags eventRegistration, const sp<IBinder>& layerHandle) {
+    const auto connection = eventThreadFor(cycle).createEventConnection(eventRegistration);
     const auto layerId = static_cast<int32_t>(LayerHandle::getLayerId(layerHandle));
 
     if (layerId != static_cast<int32_t>(UNASSIGNED_LAYER_ID)) {
@@ -355,74 +371,51 @@
     return connection;
 }
 
-sp<EventThreadConnection> Scheduler::getEventConnection(ConnectionHandle handle) {
-    std::lock_guard<std::mutex> lock(mConnectionsLock);
-    RETURN_IF_INVALID_HANDLE(handle, nullptr);
-    return mConnections[handle].connection;
+void Scheduler::dispatchHotplug(PhysicalDisplayId displayId, Hotplug hotplug) {
+    if (hasEventThreads()) {
+        const bool connected = hotplug == Hotplug::Connected;
+        eventThreadFor(Cycle::Render).onHotplugReceived(displayId, connected);
+        eventThreadFor(Cycle::LastComposite).onHotplugReceived(displayId, connected);
+    }
 }
 
-void Scheduler::onHotplugReceived(ConnectionHandle handle, PhysicalDisplayId displayId,
-                                  bool connected) {
-    android::EventThread* thread;
-    {
-        std::lock_guard<std::mutex> lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle);
-        thread = mConnections[handle].thread.get();
+void Scheduler::dispatchHotplugError(int32_t errorCode) {
+    if (hasEventThreads()) {
+        eventThreadFor(Cycle::Render).onHotplugConnectionError(errorCode);
+        eventThreadFor(Cycle::LastComposite).onHotplugConnectionError(errorCode);
     }
-
-    thread->onHotplugReceived(displayId, connected);
-}
-
-void Scheduler::onHotplugConnectionError(ConnectionHandle handle, int32_t errorCode) {
-    android::EventThread* thread;
-    {
-        std::lock_guard<std::mutex> lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle);
-        thread = mConnections[handle].thread.get();
-    }
-
-    thread->onHotplugConnectionError(errorCode);
 }
 
 void Scheduler::enableSyntheticVsync(bool enable) {
-    // TODO(b/241285945): Remove connection handles.
-    const ConnectionHandle handle = mAppConnectionHandle;
-    android::EventThread* thread;
-    {
-        std::lock_guard<std::mutex> lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle);
-        thread = mConnections[handle].thread.get();
-    }
-    thread->enableSyntheticVsync(enable);
+    eventThreadFor(Cycle::Render).enableSyntheticVsync(enable);
 }
 
-void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
+void Scheduler::onFrameRateOverridesChanged(Cycle cycle, PhysicalDisplayId displayId) {
     const bool supportsFrameRateOverrideByContent =
             pacesetterSelectorPtr()->supportsAppFrameRateOverrideByContent();
 
     std::vector<FrameRateOverride> overrides =
             mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent);
 
-    android::EventThread* thread;
-    {
-        std::lock_guard lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle);
-        thread = mConnections[handle].thread.get();
-    }
-    thread->onFrameRateOverridesChanged(displayId, std::move(overrides));
+    eventThreadFor(cycle).onFrameRateOverridesChanged(displayId, std::move(overrides));
 }
 
-void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
+void Scheduler::onHdcpLevelsChanged(Cycle cycle, PhysicalDisplayId displayId,
+                                    int32_t connectedLevel, int32_t maxLevel) {
+    eventThreadFor(cycle).onHdcpLevelsChanged(displayId, connectedLevel, maxLevel);
+}
+
+void Scheduler::onPrimaryDisplayModeChanged(Cycle cycle, const FrameRateMode& mode) {
     {
         std::lock_guard<std::mutex> lock(mPolicyLock);
         // Cache the last reported modes for primary display.
-        mPolicy.cachedModeChangedParams = {handle, mode};
+        mPolicy.cachedModeChangedParams = {cycle, mode};
 
         // Invalidate content based refresh rate selection so it could be calculated
         // again for the new refresh rate.
         mPolicy.contentRequirements.clear();
     }
-    onNonPrimaryDisplayModeChanged(handle, mode);
+    onNonPrimaryDisplayModeChanged(cycle, mode);
 }
 
 void Scheduler::dispatchCachedReportedMode() {
@@ -449,50 +442,51 @@
     }
 
     mPolicy.cachedModeChangedParams->mode = *mPolicy.modeOpt;
-    onNonPrimaryDisplayModeChanged(mPolicy.cachedModeChangedParams->handle,
+    onNonPrimaryDisplayModeChanged(mPolicy.cachedModeChangedParams->cycle,
                                    mPolicy.cachedModeChangedParams->mode);
 }
 
-void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
-    android::EventThread* thread;
-    {
-        std::lock_guard<std::mutex> lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle);
-        thread = mConnections[handle].thread.get();
+void Scheduler::onNonPrimaryDisplayModeChanged(Cycle cycle, const FrameRateMode& mode) {
+    if (hasEventThreads()) {
+        eventThreadFor(cycle).onModeChanged(mode);
     }
-    thread->onModeChanged(mode);
 }
 
-void Scheduler::dump(ConnectionHandle handle, std::string& result) const {
-    android::EventThread* thread;
-    {
-        std::lock_guard<std::mutex> lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle);
-        thread = mConnections.at(handle).thread.get();
-    }
-    thread->dump(result);
+void Scheduler::dump(Cycle cycle, std::string& result) const {
+    eventThreadFor(cycle).dump(result);
 }
 
-void Scheduler::setDuration(ConnectionHandle handle, std::chrono::nanoseconds workDuration,
+void Scheduler::setDuration(Cycle cycle, std::chrono::nanoseconds workDuration,
                             std::chrono::nanoseconds readyDuration) {
-    android::EventThread* thread;
-    {
-        std::lock_guard<std::mutex> lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle);
-        thread = mConnections[handle].thread.get();
+    if (hasEventThreads()) {
+        eventThreadFor(cycle).setDuration(workDuration, readyDuration);
     }
-    thread->setDuration(workDuration, readyDuration);
 }
 
-void Scheduler::setVsyncConfigSet(const VsyncConfigSet& configs, Period vsyncPeriod) {
-    setVsyncConfig(mVsyncModulator->setVsyncConfigSet(configs), vsyncPeriod);
+void Scheduler::updatePhaseConfiguration(Fps refreshRate) {
+    mRefreshRateStats->setRefreshRate(refreshRate);
+    mVsyncConfiguration->setRefreshRateFps(refreshRate);
+    setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()),
+                   refreshRate.getPeriod());
+}
+
+void Scheduler::resetPhaseConfiguration(Fps refreshRate) {
+    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
+    mVsyncModulator->cancelRefreshRateChange();
+
+    mVsyncConfiguration->reset();
+    updatePhaseConfiguration(refreshRate);
+}
+
+void Scheduler::setActiveDisplayPowerModeForRefreshRateStats(hal::PowerMode powerMode) {
+    mRefreshRateStats->setPowerMode(powerMode);
 }
 
 void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) {
-    setDuration(mAppConnectionHandle,
+    setDuration(Cycle::Render,
                 /* workDuration */ config.appWorkDuration,
                 /* readyDuration */ config.sfWorkDuration);
-    setDuration(mSfConnectionHandle,
+    setDuration(Cycle::LastComposite,
                 /* workDuration */ vsyncPeriod,
                 /* readyDuration */ config.sfWorkDuration);
     setDuration(config.sfWorkDuration);
@@ -549,7 +543,7 @@
 
     // On main thread to serialize reads/writes of pending hardware VSYNC state.
     static_cast<void>(
-            schedule([=]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) {
+            schedule([=, this]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) {
                 ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
 
                 if (const auto displayOpt = mDisplays.get(id)) {
@@ -563,7 +557,7 @@
             }));
 }
 
-void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) {
+void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate, bool applyImmediately) {
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
 
@@ -584,7 +578,7 @@
     ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
           to_string(mode.modePtr->getVsyncRate()).c_str());
 
-    display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
+    display.schedulePtr->getTracker().setRenderRate(renderFrameRate, applyImmediately);
 }
 
 Fps Scheduler::getNextFrameInterval(PhysicalDisplayId id,
@@ -598,11 +592,13 @@
         return Fps{};
     }
     const Display& display = *displayOpt;
-    const nsecs_t threshold =
-            display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriodNsecs() / 2;
-    const nsecs_t nextVsyncTime = display.schedulePtr->getTracker().nextAnticipatedVSyncTimeFrom(
-            currentExpectedPresentTime.ns() + threshold);
-    return Fps::fromPeriodNsecs(nextVsyncTime - currentExpectedPresentTime.ns());
+    const Duration threshold =
+            display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriod() / 2;
+    const TimePoint nextVsyncTime =
+            display.schedulePtr->vsyncDeadlineAfter(currentExpectedPresentTime + threshold,
+                                                    currentExpectedPresentTime);
+    const Duration frameInterval = nextVsyncTime - currentExpectedPresentTime;
+    return Fps::fromPeriodNsecs(frameInterval.ns());
 }
 
 void Scheduler::resync() {
@@ -856,6 +852,12 @@
     mFrameRateOverrideMappings.dump(dumper);
     dumper.eol();
 
+    mVsyncConfiguration->dump(dumper.out());
+    dumper.eol();
+
+    mRefreshRateStats->dump(dumper.out());
+    dumper.eol();
+
     {
         utils::Dumper::Section section(dumper, "Frame Targeting"sv);
 
@@ -951,16 +953,10 @@
 
 void Scheduler::applyNewVsyncSchedule(std::shared_ptr<VsyncSchedule> vsyncSchedule) {
     onNewVsyncSchedule(vsyncSchedule->getDispatch());
-    std::vector<android::EventThread*> threads;
-    {
-        std::lock_guard<std::mutex> lock(mConnectionsLock);
-        threads.reserve(mConnections.size());
-        for (auto& [_, connection] : mConnections) {
-            threads.push_back(connection.thread.get());
-        }
-    }
-    for (auto* thread : threads) {
-        thread->onNewVsyncSchedule(vsyncSchedule);
+
+    if (hasEventThreads()) {
+        eventThreadFor(Cycle::Render).onNewVsyncSchedule(vsyncSchedule);
+        eventThreadFor(Cycle::LastComposite).onNewVsyncSchedule(vsyncSchedule);
     }
 }
 
@@ -1142,38 +1138,33 @@
 auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
     ATRACE_CALL();
 
-    using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
-    ui::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
-    const auto globalSignals = makeGlobalSignals();
-    Fps pacesetterFps;
-
-    for (const auto& [id, display] : mDisplays) {
-        auto rankedFrameRates =
-                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements,
-                                                         globalSignals);
-        if (id == *mPacesetterDisplayId) {
-            pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
-        }
-        perDisplayRanking.push_back(std::move(rankedFrameRates));
-    }
-
     DisplayModeChoiceMap modeChoices;
-    using fps_approx_ops::operator==;
+    const auto globalSignals = makeGlobalSignals();
 
-    for (auto& [rankings, signals] : perDisplayRanking) {
-        const auto chosenFrameRateMode =
-                ftl::find_if(rankings,
-                             [&](const auto& ranking) {
-                                 return ranking.frameRateMode.fps == pacesetterFps;
-                             })
-                        .transform([](const auto& scoredFrameRate) {
-                            return scoredFrameRate.get().frameRateMode;
-                        })
-                        .value_or(rankings.front().frameRateMode);
+    const Fps pacesetterFps = [&]() REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext) {
+        auto rankedFrameRates =
+                pacesetterSelectorPtrLocked()->getRankedFrameRates(mPolicy.contentRequirements,
+                                                                   globalSignals);
 
-        modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(),
-                                DisplayModeChoice{chosenFrameRateMode, signals});
+        const Fps pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
+
+        modeChoices.try_emplace(*mPacesetterDisplayId,
+                                DisplayModeChoice::from(std::move(rankedFrameRates)));
+        return pacesetterFps;
+    }();
+
+    // Choose a mode for powered-on follower displays.
+    for (const auto& [id, display] : mDisplays) {
+        if (id == *mPacesetterDisplayId) continue;
+        if (display.powerMode != hal::PowerMode::ON) continue;
+
+        auto rankedFrameRates =
+                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals,
+                                                         pacesetterFps);
+
+        modeChoices.try_emplace(id, DisplayModeChoice::from(std::move(rankedFrameRates)));
     }
+
     return modeChoices;
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ce585c6..ccaa05f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -56,55 +56,38 @@
 
 #include <FrontEnd/LayerHierarchy.h>
 
-namespace android::scheduler {
-
-// Opaque handle to scheduler connection.
-struct ConnectionHandle {
-    using Id = std::uintptr_t;
-    static constexpr Id INVALID_ID = static_cast<Id>(-1);
-
-    Id id = INVALID_ID;
-
-    explicit operator bool() const { return id != INVALID_ID; }
-};
-
-inline bool operator==(ConnectionHandle lhs, ConnectionHandle rhs) {
-    return lhs.id == rhs.id;
-}
-
-} // namespace android::scheduler
-
-namespace std {
-
-template <>
-struct hash<android::scheduler::ConnectionHandle> {
-    size_t operator()(android::scheduler::ConnectionHandle handle) const {
-        return hash<android::scheduler::ConnectionHandle::Id>()(handle.id);
-    }
-};
-
-} // namespace std
-
 namespace android {
 
 class FenceTime;
+class TimeStats;
 
 namespace frametimeline {
 class TokenManager;
 } // namespace frametimeline
 
+namespace surfaceflinger {
+class Factory;
+} // namespace surfaceflinger
+
 namespace scheduler {
 
 using GlobalSignals = RefreshRateSelector::GlobalSignals;
 
+class RefreshRateStats;
+class VsyncConfiguration;
 class VsyncSchedule;
 
+enum class Cycle {
+    Render,       // Surface rendering.
+    LastComposite // Ahead of display compositing by one refresh period.
+};
+
 class Scheduler : public IEventThreadCallback, android::impl::MessageQueue {
     using Impl = android::impl::MessageQueue;
 
 public:
-    Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, sp<VsyncModulator>,
-              IVsyncTrackerCallback&);
+    Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, surfaceflinger::Factory&,
+              Fps activeRefreshRate, TimeStats&);
     virtual ~Scheduler();
 
     void startTimers();
@@ -124,11 +107,11 @@
 
     void run();
 
-    using Impl::initVsync;
+    void initVsync(frametimeline::TokenManager&, std::chrono::nanoseconds workDuration);
 
-    using Impl::getScheduledFrameTime;
     using Impl::setDuration;
 
+    using Impl::getScheduledFrameResult;
     using Impl::scheduleConfigure;
     using Impl::scheduleFrame;
 
@@ -147,34 +130,34 @@
         return std::move(future);
     }
 
-    enum class Cycle {
-        Render,       // Surface rendering.
-        LastComposite // Ahead of display compositing by one refresh period.
-    };
-
-    ConnectionHandle createEventThread(Cycle, frametimeline::TokenManager*,
-                                       std::chrono::nanoseconds workDuration,
-                                       std::chrono::nanoseconds readyDuration);
+    void createEventThread(Cycle, frametimeline::TokenManager*,
+                           std::chrono::nanoseconds workDuration,
+                           std::chrono::nanoseconds readyDuration);
 
     sp<IDisplayEventConnection> createDisplayEventConnection(
-            ConnectionHandle, EventRegistrationFlags eventRegistration = {},
+            Cycle, EventRegistrationFlags eventRegistration = {},
             const sp<IBinder>& layerHandle = nullptr) EXCLUDES(mChoreographerLock);
 
-    sp<EventThreadConnection> getEventConnection(ConnectionHandle);
+    const sp<EventThreadConnection>& getEventConnection(Cycle cycle) const {
+        return cycle == Cycle::Render ? mRenderEventConnection : mLastCompositeEventConnection;
+    }
 
-    void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
-    void onHotplugConnectionError(ConnectionHandle, int32_t errorCode);
+    enum class Hotplug { Connected, Disconnected };
+    void dispatchHotplug(PhysicalDisplayId, Hotplug);
 
-    void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock);
-    void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&);
+    void dispatchHotplugError(int32_t errorCode);
+
+    void onPrimaryDisplayModeChanged(Cycle, const FrameRateMode&) EXCLUDES(mPolicyLock);
+    void onNonPrimaryDisplayModeChanged(Cycle, const FrameRateMode&);
 
     void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext);
 
-    void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId)
-            EXCLUDES(mConnectionsLock);
+    void onFrameRateOverridesChanged(Cycle, PhysicalDisplayId);
+
+    void onHdcpLevelsChanged(Cycle, PhysicalDisplayId, int32_t, int32_t);
 
     // Modifies work duration in the event thread.
-    void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration,
+    void setDuration(Cycle, std::chrono::nanoseconds workDuration,
                      std::chrono::nanoseconds readyDuration);
 
     VsyncModulator& vsyncModulator() { return *mVsyncModulator; }
@@ -199,10 +182,13 @@
         }
     }
 
-    void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod);
+    void updatePhaseConfiguration(Fps);
+    void resetPhaseConfiguration(Fps) REQUIRES(kMainThreadContext);
+
+    const VsyncConfiguration& getVsyncConfiguration() const { return *mVsyncConfiguration; }
 
     // Sets the render rate for the scheduler to run at.
-    void setRenderRate(PhysicalDisplayId, Fps);
+    void setRenderRate(PhysicalDisplayId, Fps, bool applyImmediately);
 
     void enableHardwareVsync(PhysicalDisplayId) REQUIRES(kMainThreadContext);
     void disableHardwareVsync(PhysicalDisplayId, bool disallow) REQUIRES(kMainThreadContext);
@@ -247,8 +233,10 @@
     // Indicates that touch interaction is taking place.
     void onTouchHint();
 
-    void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode)
-            REQUIRES(kMainThreadContext);
+    void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode) REQUIRES(kMainThreadContext);
+
+    // TODO(b/255635821): Track this per display.
+    void setActiveDisplayPowerModeForRefreshRateStats(hal::PowerMode) REQUIRES(kMainThreadContext);
 
     ConstVsyncSchedulePtr getVsyncSchedule(std::optional<PhysicalDisplayId> = std::nullopt) const
             EXCLUDES(mDisplayLock);
@@ -274,7 +262,7 @@
     bool isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const;
 
     void dump(utils::Dumper&) const;
-    void dump(ConnectionHandle, std::string&) const;
+    void dump(Cycle, std::string&) const;
     void dumpVsync(std::string&) const EXCLUDES(mDisplayLock);
 
     // Returns the preferred refresh rate and frame rate for the pacesetter display.
@@ -355,8 +343,17 @@
     void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) override
             REQUIRES(kMainThreadContext, mDisplayLock);
 
-    // Create a connection on the given EventThread.
-    ConnectionHandle createConnection(std::unique_ptr<EventThread>);
+    // Used to skip event dispatch before EventThread creation during boot.
+    // TODO: b/241285191 - Reorder Scheduler initialization to avoid this.
+    bool hasEventThreads() const {
+        return CC_LIKELY(
+                mRenderEventThread &&
+                (FlagManager::getInstance().deprecate_vsync_sf() || mLastCompositeEventThread));
+    }
+
+    EventThread& eventThreadFor(Cycle cycle) const {
+        return *(cycle == Cycle::Render ? mRenderEventThread : mLastCompositeEventThread);
+    }
 
     // Update feature state machine to given state when corresponding timer resets or expires.
     void kernelIdleTimerCallback(TimerState) EXCLUDES(mDisplayLock);
@@ -407,6 +404,11 @@
         DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals)
               : mode(std::move(mode)), consideredSignals(consideredSignals) {}
 
+        static DisplayModeChoice from(RefreshRateSelector::RankedFrameRates rankedFrameRates) {
+            return {rankedFrameRates.ranking.front().frameRateMode,
+                    rankedFrameRates.consideredSignals};
+        }
+
         FrameRateMode mode;
         GlobalSignals consideredSignals;
 
@@ -442,29 +444,29 @@
 
     // IEventThreadCallback overrides
     bool throttleVsync(TimePoint, uid_t) override;
+    // Get frame interval
     Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
     void resync() override EXCLUDES(mDisplayLock);
+    void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
 
-    // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
-    struct Connection {
-        sp<EventThreadConnection> connection;
-        std::unique_ptr<EventThread> thread;
-    };
+    std::unique_ptr<EventThread> mRenderEventThread;
+    sp<EventThreadConnection> mRenderEventConnection;
 
-    ConnectionHandle::Id mNextConnectionHandleId = 0;
-    mutable std::mutex mConnectionsLock;
-    std::unordered_map<ConnectionHandle, Connection> mConnections GUARDED_BY(mConnectionsLock);
-
-    ConnectionHandle mAppConnectionHandle;
-    ConnectionHandle mSfConnectionHandle;
+    std::unique_ptr<EventThread> mLastCompositeEventThread;
+    sp<EventThreadConnection> mLastCompositeEventConnection;
 
     std::atomic<nsecs_t> mLastResyncTime = 0;
 
     const FeatureFlags mFeatures;
 
+    // Stores phase offsets configured per refresh rate.
+    const std::unique_ptr<VsyncConfiguration> mVsyncConfiguration;
+
     // Shifts the VSYNC phase during certain transactions and refresh rate changes.
     const sp<VsyncModulator> mVsyncModulator;
 
+    const std::unique_ptr<RefreshRateStats> mRefreshRateStats;
+
     // Used to choose refresh rate if content detection is enabled.
     LayerHistory mLayerHistory;
 
@@ -478,8 +480,6 @@
 
     ISchedulerCallback& mSchedulerCallback;
 
-    IVsyncTrackerCallback& mVsyncTrackerCallback;
-
     // mDisplayLock may be locked while under mPolicyLock.
     mutable std::mutex mPolicyLock;
 
@@ -495,9 +495,7 @@
               : displayId(displayId),
                 selectorPtr(std::move(selectorPtr)),
                 schedulePtr(std::move(schedulePtr)),
-                targeterPtr(std::make_unique<
-                            FrameTargeter>(displayId,
-                                           features.test(Feature::kBackpressureGpuComposition))) {}
+                targeterPtr(std::make_unique<FrameTargeter>(displayId, features)) {}
 
         const PhysicalDisplayId displayId;
 
@@ -569,7 +567,7 @@
         ftl::Optional<FrameRateMode> modeOpt;
 
         struct ModeChangedParams {
-            ConnectionHandle handle;
+            Cycle cycle;
             FrameRateMode mode;
         };
 
diff --git a/services/surfaceflinger/Scheduler/StrongTyping.h b/services/surfaceflinger/Scheduler/StrongTyping.h
deleted file mode 100644
index a05c123..0000000
--- a/services/surfaceflinger/Scheduler/StrongTyping.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2019 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
-namespace android {
-
-template <typename T, template <typename> class AbilityType>
-struct Ability {
-    T& base() { return static_cast<T&>(*this); }
-    T const& base() const { return static_cast<T const&>(*this); }
-};
-
-template <typename T>
-struct Add : Ability<T, Add> {
-    inline T operator+(T const& other) const { return T(this->base().value() + other.value()); }
-    inline T& operator++() {
-        ++this->base().value();
-        return this->base();
-    };
-    inline T operator++(int) {
-        T tmp(this->base());
-        operator++();
-        return tmp;
-    };
-    inline T& operator+=(T const& other) {
-        this->base().value() += other.value();
-        return this->base();
-    };
-};
-
-template <typename T>
-struct Compare : Ability<T, Compare> {
-    inline bool operator==(T const& other) const { return this->base().value() == other.value(); };
-    inline bool operator<(T const& other) const { return this->base().value() < other.value(); }
-    inline bool operator<=(T const& other) const { return (*this < other) || (*this == other); }
-    inline bool operator!=(T const& other) const { return !(*this == other); }
-    inline bool operator>=(T const& other) const { return !(*this < other); }
-    inline bool operator>(T const& other) const { return !(*this < other || *this == other); }
-};
-
-template <typename T>
-struct Hash : Ability<T, Hash> {
-    [[nodiscard]] std::size_t hash() const {
-        return std::hash<typename std::remove_const<
-                typename std::remove_reference<decltype(this->base().value())>::type>::type>{}(
-                this->base().value());
-    }
-};
-
-template <typename T, typename W, template <typename> class... Ability>
-struct StrongTyping : Ability<StrongTyping<T, W, Ability...>>... {
-    constexpr StrongTyping() = default;
-    constexpr explicit StrongTyping(T const& value) : mValue(value) {}
-    StrongTyping(StrongTyping const&) = default;
-    StrongTyping& operator=(StrongTyping const&) = default;
-    explicit inline operator T() const { return mValue; }
-    T const& value() const { return mValue; }
-    T& value() { return mValue; }
-
-    friend std::ostream& operator<<(std::ostream& os, const StrongTyping<T, W, Ability...>& value) {
-        return os << value.value();
-    }
-
-private:
-    T mValue{0};
-};
-} // namespace android
-
-namespace std {
-template <typename T, typename W, template <typename> class... Ability>
-struct hash<android::StrongTyping<T, W, Ability...>> {
-    std::size_t operator()(android::StrongTyping<T, W, Ability...> const& k) const {
-        return k.hash();
-    }
-};
-} // namespace std
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index c3a952f..0c43ffb 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -20,13 +20,16 @@
 #include <optional>
 #include <string>
 
+#include <ftl/mixins.h>
+#include <scheduler/Time.h>
 #include <utils/Timers.h>
 
-#include "StrongTyping.h"
-
 namespace android::scheduler {
 
-using ScheduleResult = std::optional<nsecs_t>;
+struct ScheduleResult {
+    TimePoint callbackTime;
+    TimePoint vsyncTime;
+};
 
 enum class CancelResult { Cancelled, TooLate, Error };
 
@@ -35,7 +38,11 @@
  */
 class VSyncDispatch {
 public:
-    using CallbackToken = StrongTyping<size_t, class CallbackTokenTag, Compare, Hash>;
+    struct CallbackToken : ftl::DefaultConstructible<CallbackToken, size_t>,
+                           ftl::Equatable<CallbackToken>,
+                           ftl::Incrementable<CallbackToken> {
+        using DefaultConstructible::DefaultConstructible;
+    };
 
     virtual ~VSyncDispatch();
 
@@ -84,8 +91,8 @@
      *                 able to provide the ready-by time (deadline) on the callback.
      *                 For internal clients, we don't need to add additional padding, so
      *                 readyDuration will typically be 0.
-     * @earliestVsync: The targeted display time. This will be snapped to the closest
-     *                 predicted vsync time after earliestVsync.
+     * @lastVsync: The targeted display time. This will be snapped to the closest
+     *                 predicted vsync time after lastVsync.
      *
      * callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync
      * event.
@@ -93,11 +100,11 @@
     struct ScheduleTiming {
         nsecs_t workDuration = 0;
         nsecs_t readyDuration = 0;
-        nsecs_t earliestVsync = 0;
+        nsecs_t lastVsync = 0;
 
         bool operator==(const ScheduleTiming& other) const {
             return workDuration == other.workDuration && readyDuration == other.readyDuration &&
-                    earliestVsync == other.earliestVsync;
+                    lastVsync == other.lastVsync;
         }
 
         bool operator!=(const ScheduleTiming& other) const { return !(*this == other); }
@@ -109,22 +116,24 @@
      * The callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync
      * event.
      *
-     * The caller designates the earliest vsync event that should be targeted by the earliestVsync
+     * The caller designates the earliest vsync event that should be targeted by the lastVsync
      * parameter.
      * The callback will be scheduled at (workDuration + readyDuration - predictedVsync), where
-     * predictedVsync is the first vsync event time where ( predictedVsync >= earliestVsync ).
+     * predictedVsync is the first vsync event time where ( predictedVsync >= lastVsync ).
      *
-     * If (workDuration + readyDuration - earliestVsync) is in the past, or if a callback has
+     * If (workDuration + readyDuration - lastVsync) is in the past, or if a callback has
      * already been dispatched for the predictedVsync, an error will be returned.
      *
      * It is valid to reschedule a callback to a different time.
      *
      * \param [in] token           The callback to schedule.
      * \param [in] scheduleTiming  The timing information for this schedule call
-     * \return                     The expected callback time if a callback was scheduled.
+     * \return                     The expected callback time if a callback was scheduled,
+     *                             along with VSYNC time for the callback scheduled.
      *                             std::nullopt if the callback is not registered.
      */
-    virtual ScheduleResult schedule(CallbackToken token, ScheduleTiming scheduleTiming) = 0;
+    virtual std::optional<ScheduleResult> schedule(CallbackToken token,
+                                                   ScheduleTiming scheduleTiming) = 0;
 
     /*
      * Update the timing information for a scheduled callback.
@@ -132,10 +141,12 @@
      *
      * \param [in] token           The callback to schedule.
      * \param [in] scheduleTiming  The timing information for this schedule call
-     * \return                     The expected callback time if a callback was scheduled.
+     * \return                     The expected callback time if a callback was scheduled,
+     *                             along with VSYNC time for the callback scheduled.
      *                             std::nullopt if the callback is not registered.
      */
-    virtual ScheduleResult update(CallbackToken token, ScheduleTiming scheduleTiming) = 0;
+    virtual std::optional<ScheduleResult> update(CallbackToken token,
+                                                 ScheduleTiming scheduleTiming) = 0;
 
     /* Cancels a scheduled callback, if possible.
      *
@@ -165,10 +176,10 @@
     VSyncCallbackRegistration& operator=(VSyncCallbackRegistration&&);
 
     // See documentation for VSyncDispatch::schedule.
-    ScheduleResult schedule(VSyncDispatch::ScheduleTiming scheduleTiming);
+    std::optional<ScheduleResult> schedule(VSyncDispatch::ScheduleTiming scheduleTiming);
 
     // See documentation for VSyncDispatch::update.
-    ScheduleResult update(VSyncDispatch::ScheduleTiming scheduleTiming);
+    std::optional<ScheduleResult> update(VSyncDispatch::ScheduleTiming scheduleTiming);
 
     // See documentation for VSyncDispatch::cancel.
     CancelResult cancel();
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index ef30887..6d6b70d 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -20,7 +20,7 @@
 
 #include <android-base/stringprintf.h>
 #include <ftl/concat.h>
-#include <utils/Trace.h>
+#include <gui/TraceUtils.h>
 #include <log/log_main.h>
 
 #include <scheduler/TimeKeeper.h>
@@ -38,16 +38,21 @@
 
 namespace {
 
-nsecs_t getExpectedCallbackTime(nsecs_t nextVsyncTime,
-                                const VSyncDispatch::ScheduleTiming& timing) {
-    return nextVsyncTime - timing.readyDuration - timing.workDuration;
+ScheduleResult getExpectedCallbackTime(nsecs_t nextVsyncTime,
+                                       const VSyncDispatch::ScheduleTiming& timing) {
+    return {TimePoint::fromNs(nextVsyncTime - timing.readyDuration - timing.workDuration),
+            TimePoint::fromNs(nextVsyncTime)};
 }
 
-nsecs_t getExpectedCallbackTime(VSyncTracker& tracker, nsecs_t now,
-                                const VSyncDispatch::ScheduleTiming& timing) {
-    const auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
-            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
-    return getExpectedCallbackTime(nextVsyncTime, timing);
+void traceEntry(const VSyncDispatchTimerQueueEntry& entry, nsecs_t now) {
+    if (!ATRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) {
+        return;
+    }
+
+    ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ",
+                      ns2us(*entry.wakeupTime() - now), "us; VSYNC in ",
+                      ns2us(*entry.targetVsync() - now), "us");
+    ATRACE_FORMAT_INSTANT(trace.c_str());
 }
 
 } // namespace
@@ -93,15 +98,21 @@
 
 ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing,
                                                       VSyncTracker& tracker, nsecs_t now) {
-    auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
-            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
+    ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule");
+    auto nextVsyncTime =
+            tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
+                                                          now + timing.workDuration +
+                                                                  timing.readyDuration),
+                                                 timing.lastVsync);
     auto nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
 
     bool const wouldSkipAVsyncTarget =
             mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
     bool const wouldSkipAWakeup =
             mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
-    if (FlagManager::getInstance().dont_skip_on_early()) {
+    ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
+                          wouldSkipAVsyncTarget, wouldSkipAWakeup);
+    if (FlagManager::getInstance().dont_skip_on_early_ro()) {
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
             nextVsyncTime = mArmedInfo->mActualVsyncTime;
         } else {
@@ -119,11 +130,15 @@
     auto const nextReadyTime = nextVsyncTime - timing.readyDuration;
     mScheduleTiming = timing;
     mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
-    return nextWakeupTime;
+    return ScheduleResult{TimePoint::fromNs(nextWakeupTime), TimePoint::fromNs(nextVsyncTime)};
 }
 
-void VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming timing) {
+ScheduleResult VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(
+        VSyncTracker& tracker, nsecs_t now, VSyncDispatch::ScheduleTiming timing) {
     mWorkloadUpdateInfo = timing;
+    const auto armedInfo = getArmedInfo(tracker, now, timing, mArmedInfo);
+    return {TimePoint::fromNs(armedInfo.mActualWakeupTime),
+            TimePoint::fromNs(armedInfo.mActualVsyncTime)};
 }
 
 bool VSyncDispatchTimerQueueEntry::hasPendingWorkloadUpdate() const {
@@ -139,36 +154,68 @@
     bool const nextVsyncTooClose = mLastDispatchTime &&
             (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod;
     if (alreadyDispatchedForVsync) {
-        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance);
+        ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync");
+        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance,
+                                                    *mLastDispatchTime);
     }
 
     if (nextVsyncTooClose) {
-        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod);
+        ATRACE_FORMAT_INSTANT("nextVsyncTooClose");
+        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod,
+                                                    *mLastDispatchTime + currentPeriod);
     }
 
     return nextVsyncTime;
 }
 
+auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now,
+                                                VSyncDispatch::ScheduleTiming timing,
+                                                std::optional<ArmingInfo> armedInfo) const
+        -> ArmingInfo {
+    ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo");
+    const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration;
+    const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync);
+
+    const auto nextVsyncTime =
+            adjustVsyncIfNeeded(tracker, /*nextVsyncTime*/
+                                tracker.nextAnticipatedVSyncTimeFrom(earliestVsync,
+                                                                     timing.lastVsync));
+    const auto nextReadyTime = nextVsyncTime - timing.readyDuration;
+    const auto nextWakeupTime = nextReadyTime - timing.workDuration;
+
+    if (FlagManager::getInstance().dont_skip_on_early_ro()) {
+        bool const wouldSkipAVsyncTarget =
+                armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance));
+        bool const wouldSkipAWakeup =
+                armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance));
+        ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
+                              wouldSkipAVsyncTarget, wouldSkipAWakeup);
+        if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
+            return *armedInfo;
+        }
+    }
+
+    return ArmingInfo{nextWakeupTime, nextVsyncTime, nextReadyTime};
+}
+
 void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) {
+    ATRACE_NAME("VSyncDispatchTimerQueueEntry::update");
     if (!mArmedInfo && !mWorkloadUpdateInfo) {
         return;
     }
 
     if (mWorkloadUpdateInfo) {
+        const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration;
+        const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration;
+        const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync;
+        ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64
+                              " lastVsyncDelta=%" PRId64,
+                              workDelta, readyDelta, lastVsyncDelta);
         mScheduleTiming = *mWorkloadUpdateInfo;
         mWorkloadUpdateInfo.reset();
     }
 
-    const auto earliestReadyBy = now + mScheduleTiming.workDuration + mScheduleTiming.readyDuration;
-    const auto earliestVsync = std::max(earliestReadyBy, mScheduleTiming.earliestVsync);
-
-    const auto nextVsyncTime =
-            adjustVsyncIfNeeded(tracker, /*nextVsyncTime*/
-                                tracker.nextAnticipatedVSyncTimeFrom(earliestVsync));
-    const auto nextReadyTime = nextVsyncTime - mScheduleTiming.readyDuration;
-    const auto nextWakeupTime = nextReadyTime - mScheduleTiming.workDuration;
-
-    mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
+    mArmedInfo = getArmedInfo(tracker, now, mScheduleTiming, mArmedInfo);
 }
 
 void VSyncDispatchTimerQueueEntry::disarm() {
@@ -214,10 +261,10 @@
     StringAppendF(&result, "\t\t%s: %s %s\n", mName.c_str(),
                   mRunning ? "(in callback function)" : "", armedInfo.c_str());
     StringAppendF(&result,
-                  "\t\t\tworkDuration: %.2fms readyDuration: %.2fms earliestVsync: %.2fms relative "
+                  "\t\t\tworkDuration: %.2fms readyDuration: %.2fms lastVsync: %.2fms relative "
                   "to now\n",
                   mScheduleTiming.workDuration / 1e6f, mScheduleTiming.readyDuration / 1e6f,
-                  (mScheduleTiming.earliestVsync - systemTime()) / 1e6f);
+                  (mScheduleTiming.lastVsync - systemTime()) / 1e6f);
 
     if (mLastDispatchTime) {
         StringAppendF(&result, "\t\t\tmLastDispatchTime: %.2fms ago\n",
@@ -237,6 +284,7 @@
 
 VSyncDispatchTimerQueue::~VSyncDispatchTimerQueue() {
     std::lock_guard lock(mMutex);
+    mRunning = false;
     cancelTimer();
     for (auto& [_, entry] : mCallbacks) {
         ALOGE("Forgot to unregister a callback on VSyncDispatch!");
@@ -257,15 +305,16 @@
 }
 
 void VSyncDispatchTimerQueue::rearmTimer(nsecs_t now) {
-    rearmTimerSkippingUpdateFor(now, mCallbacks.end());
+    rearmTimerSkippingUpdateFor(now, mCallbacks.cend());
 }
 
 void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor(
-        nsecs_t now, CallbackMap::iterator const& skipUpdateIt) {
+        nsecs_t now, CallbackMap::const_iterator skipUpdateIt) {
+    ATRACE_CALL();
     std::optional<nsecs_t> min;
     std::optional<nsecs_t> targetVsync;
     std::optional<std::string_view> nextWakeupName;
-    for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
+    for (auto it = mCallbacks.cbegin(); it != mCallbacks.cend(); ++it) {
         auto& callback = it->second;
         if (!callback->wakeupTime() && !callback->hasPendingWorkloadUpdate()) {
             continue;
@@ -274,7 +323,10 @@
         if (it != skipUpdateIt) {
             callback->update(*mTracker, now);
         }
-        auto const wakeupTime = *callback->wakeupTime();
+
+        traceEntry(*callback, now);
+
+        const auto wakeupTime = *callback->wakeupTime();
         if (!min || *min > wakeupTime) {
             nextWakeupName = callback->name();
             min = wakeupTime;
@@ -283,11 +335,6 @@
     }
 
     if (min && min < mIntendedWakeupTime) {
-        if (ATRACE_ENABLED() && nextWakeupName && targetVsync) {
-            ftl::Concat trace(ftl::truncated<5>(*nextWakeupName), " alarm in ", ns2us(*min - now),
-                              "us; VSYNC in ", ns2us(*targetVsync - now), "us");
-            ATRACE_NAME(trace.c_str());
-        }
         setTimer(*min, now);
     } else {
         ATRACE_NAME("cancel timer");
@@ -296,6 +343,7 @@
 }
 
 void VSyncDispatchTimerQueue::timerCallback() {
+    ATRACE_CALL();
     struct Invocation {
         std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;
         nsecs_t vsyncTimestamp;
@@ -305,6 +353,10 @@
     std::vector<Invocation> invocations;
     {
         std::lock_guard lock(mMutex);
+        if (!mRunning) {
+            ALOGD("TimerQueue is not running. Skipping callback.");
+            return;
+        }
         auto const now = mTimeKeeper->now();
         mLastTimerCallback = now;
         for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
@@ -314,8 +366,9 @@
                 continue;
             }
 
-            auto const readyTime = callback->readyTime();
+            traceEntry(*callback, now);
 
+            auto const readyTime = callback->readyTime();
             auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast<nsecs_t>(0));
             if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) {
                 callback->executing();
@@ -329,6 +382,8 @@
     }
 
     for (auto const& invocation : invocations) {
+        ftl::Concat trace(ftl::truncated<5>(invocation.callback->name()));
+        ATRACE_FORMAT("%s: %s", __func__, trace.c_str());
         invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,
                                       invocation.deadlineTimestamp);
     }
@@ -337,13 +392,12 @@
 VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(
         Callback callback, std::string callbackName) {
     std::lock_guard lock(mMutex);
-    return CallbackToken{
-            mCallbacks
-                    .emplace(++mCallbackToken,
-                             std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),
-                                                                            std::move(callback),
-                                                                            mMinVsyncDistance))
-                    .first->first};
+    return mCallbacks
+            .try_emplace(++mCallbackToken,
+                         std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),
+                                                                        std::move(callback),
+                                                                        mMinVsyncDistance))
+            .first->first;
 }
 
 void VSyncDispatchTimerQueue::unregisterCallback(CallbackToken token) {
@@ -353,7 +407,7 @@
         auto it = mCallbacks.find(token);
         if (it != mCallbacks.end()) {
             entry = it->second;
-            mCallbacks.erase(it);
+            mCallbacks.erase(it->first);
         }
     }
 
@@ -362,14 +416,14 @@
     }
 }
 
-ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token,
-                                                 ScheduleTiming scheduleTiming) {
+std::optional<ScheduleResult> VSyncDispatchTimerQueue::schedule(CallbackToken token,
+                                                                ScheduleTiming scheduleTiming) {
     std::lock_guard lock(mMutex);
     return scheduleLocked(token, scheduleTiming);
 }
 
-ScheduleResult VSyncDispatchTimerQueue::scheduleLocked(CallbackToken token,
-                                                       ScheduleTiming scheduleTiming) {
+std::optional<ScheduleResult> VSyncDispatchTimerQueue::scheduleLocked(
+        CallbackToken token, ScheduleTiming scheduleTiming) {
     auto it = mCallbacks.find(token);
     if (it == mCallbacks.end()) {
         return {};
@@ -381,14 +435,10 @@
      * timer recalculation to avoid cancelling a callback that is about to fire. */
     auto const rearmImminent = now > mIntendedWakeupTime;
     if (CC_UNLIKELY(rearmImminent)) {
-        callback->addPendingWorkloadUpdate(scheduleTiming);
-        return getExpectedCallbackTime(*mTracker, now, scheduleTiming);
+        return callback->addPendingWorkloadUpdate(*mTracker, now, scheduleTiming);
     }
 
-    const ScheduleResult result = callback->schedule(scheduleTiming, *mTracker, now);
-    if (!result.has_value()) {
-        return {};
-    }
+    const auto result = callback->schedule(scheduleTiming, *mTracker, now);
 
     if (callback->wakeupTime() < mIntendedWakeupTime - mTimerSlack) {
         rearmTimerSkippingUpdateFor(now, it);
@@ -397,7 +447,8 @@
     return result;
 }
 
-ScheduleResult VSyncDispatchTimerQueue::update(CallbackToken token, ScheduleTiming scheduleTiming) {
+std::optional<ScheduleResult> VSyncDispatchTimerQueue::update(CallbackToken token,
+                                                              ScheduleTiming scheduleTiming) {
     std::lock_guard lock(mMutex);
     const auto it = mCallbacks.find(token);
     if (it == mCallbacks.end()) {
@@ -474,14 +525,16 @@
     if (mToken) mDispatch->unregisterCallback(*mToken);
 }
 
-ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) {
+std::optional<ScheduleResult> VSyncCallbackRegistration::schedule(
+        VSyncDispatch::ScheduleTiming scheduleTiming) {
     if (!mToken) {
         return std::nullopt;
     }
     return mDispatch->schedule(*mToken, scheduleTiming);
 }
 
-ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) {
+std::optional<ScheduleResult> VSyncCallbackRegistration::update(
+        VSyncDispatch::ScheduleTiming scheduleTiming) {
     if (!mToken) {
         return std::nullopt;
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index e0fb8f9..e4ddc03 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -16,14 +16,13 @@
 
 #pragma once
 
-#include <functional>
 #include <memory>
 #include <mutex>
 #include <string>
 #include <string_view>
-#include <unordered_map>
 
 #include <android-base/thread_annotations.h>
+#include <ftl/small_map.h>
 
 #include "VSyncDispatch.h"
 #include "VsyncSchedule.h"
@@ -70,7 +69,8 @@
 
     // Adds a pending upload of the earliestVSync and workDuration that will be applied on the next
     // call to update()
-    void addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming);
+    ScheduleResult addPendingWorkloadUpdate(VSyncTracker&, nsecs_t now,
+                                            VSyncDispatch::ScheduleTiming);
 
     // Checks if there is a pending update to the workload, returning true if so.
     bool hasPendingWorkloadUpdate() const;
@@ -84,7 +84,15 @@
     void dump(std::string& result) const;
 
 private:
+    struct ArmingInfo {
+        nsecs_t mActualWakeupTime;
+        nsecs_t mActualVsyncTime;
+        nsecs_t mActualReadyTime;
+    };
+
     nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const;
+    ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming,
+                            std::optional<ArmingInfo>) const;
 
     const std::string mName;
     const VSyncDispatch::Callback mCallback;
@@ -92,11 +100,6 @@
     VSyncDispatch::ScheduleTiming mScheduleTiming;
     const nsecs_t mMinVsyncDistance;
 
-    struct ArmingInfo {
-        nsecs_t mActualWakeupTime;
-        nsecs_t mActualVsyncTime;
-        nsecs_t mActualReadyTime;
-    };
     std::optional<ArmingInfo> mArmedInfo;
     std::optional<nsecs_t> mLastDispatchTime;
 
@@ -126,8 +129,8 @@
 
     CallbackToken registerCallback(Callback, std::string callbackName) final;
     void unregisterCallback(CallbackToken) final;
-    ScheduleResult schedule(CallbackToken, ScheduleTiming) final;
-    ScheduleResult update(CallbackToken, ScheduleTiming) final;
+    std::optional<ScheduleResult> schedule(CallbackToken, ScheduleTiming) final;
+    std::optional<ScheduleResult> update(CallbackToken, ScheduleTiming) final;
     CancelResult cancel(CallbackToken) final;
     void dump(std::string&) const final;
 
@@ -135,26 +138,31 @@
     VSyncDispatchTimerQueue(const VSyncDispatchTimerQueue&) = delete;
     VSyncDispatchTimerQueue& operator=(const VSyncDispatchTimerQueue&) = delete;
 
+    // The static capacity was chosen to exceed the expected number of callbacks.
     using CallbackMap =
-            std::unordered_map<CallbackToken, std::shared_ptr<VSyncDispatchTimerQueueEntry>>;
+            ftl::SmallMap<CallbackToken, std::shared_ptr<VSyncDispatchTimerQueueEntry>, 5>;
 
     void timerCallback();
     void setTimer(nsecs_t, nsecs_t) REQUIRES(mMutex);
     void rearmTimer(nsecs_t now) REQUIRES(mMutex);
-    void rearmTimerSkippingUpdateFor(nsecs_t now, CallbackMap::iterator const& skipUpdate)
+    void rearmTimerSkippingUpdateFor(nsecs_t now, CallbackMap::const_iterator skipUpdate)
             REQUIRES(mMutex);
     void cancelTimer() REQUIRES(mMutex);
-    ScheduleResult scheduleLocked(CallbackToken, ScheduleTiming) REQUIRES(mMutex);
+    std::optional<ScheduleResult> scheduleLocked(CallbackToken, ScheduleTiming) REQUIRES(mMutex);
 
     std::mutex mutable mMutex;
 
+    // During VSyncDispatchTimerQueue deconstruction, skip timerCallback to
+    // avoid crash
+    bool mRunning = true;
+
     static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max();
     std::unique_ptr<TimeKeeper> const mTimeKeeper;
     VsyncSchedule::TrackerPtr mTracker;
     nsecs_t const mTimerSlack;
     nsecs_t const mMinVsyncDistance;
 
-    size_t mCallbackToken GUARDED_BY(mMutex) = 0;
+    CallbackToken mCallbackToken GUARDED_BY(mMutex);
 
     CallbackMap mCallbacks GUARDED_BY(mMutex);
     nsecs_t mIntendedWakeupTime GUARDED_BY(mMutex) = kInvalidTime;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 0d79a39..85ce713 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -45,18 +45,28 @@
 
 static auto constexpr kMaxPercent = 100u;
 
+namespace {
+int numVsyncsPerFrame(const ftl::NonNull<DisplayModePtr>& displayModePtr) {
+    const auto idealPeakRefreshPeriod = displayModePtr->getPeakFps().getPeriodNsecs();
+    const auto idealRefreshPeriod = displayModePtr->getVsyncRate().getPeriodNsecs();
+    return static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
+                                       static_cast<float>(idealRefreshPeriod)));
+}
+} // namespace
+
 VSyncPredictor::~VSyncPredictor() = default;
 
-VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
-                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
-                               IVsyncTrackerCallback& callback)
-      : mId(modePtr->getPhysicalDisplayId()),
+VSyncPredictor::VSyncPredictor(std::unique_ptr<Clock> clock, ftl::NonNull<DisplayModePtr> modePtr,
+                               size_t historySize, size_t minimumSamplesForPrediction,
+                               uint32_t outlierTolerancePercent)
+      : mClock(std::move(clock)),
+        mId(modePtr->getPhysicalDisplayId()),
         mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
-        mVsyncTrackerCallback(callback),
-        mDisplayModePtr(modePtr) {
+        mDisplayModePtr(modePtr),
+        mNumVsyncsForFrame(numVsyncsPerFrame(mDisplayModePtr)) {
     resetModel();
 }
 
@@ -120,11 +130,8 @@
 }
 
 Period VSyncPredictor::minFramePeriodLocked() const {
-    const auto idealPeakRefreshPeriod = mDisplayModePtr->getPeakFps().getPeriodNsecs();
-    const auto numPeriods = static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
-                                                        static_cast<float>(idealPeriod())));
     const auto slope = mRateMap.find(idealPeriod())->second.slope;
-    return Period::fromNs(slope * numPeriods);
+    return Period::fromNs(slope * mNumVsyncsForFrame);
 }
 
 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
@@ -149,7 +156,7 @@
             mKnownTimestamp = timestamp;
         }
         ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
-            (systemTime() - *mKnownTimestamp) / 1e6f);
+                              (mClock->now() - *mKnownTimestamp) / 1e6f);
         return false;
     }
 
@@ -252,18 +259,7 @@
     return true;
 }
 
-auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence {
-    const auto vsync = nextAnticipatedVSyncTimeFromLocked(timestamp);
-    if (!mLastVsyncSequence) return {vsync, 0};
-
-    const auto [slope, _] = getVSyncPredictionModelLocked();
-    const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence;
-    const auto vsyncSequence = lastVsyncSequence +
-            static_cast<int64_t>(std::round((vsync - lastVsyncTime) / static_cast<float>(slope)));
-    return {vsync, vsyncSequence};
-}
-
-nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const {
+nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const {
     auto const [slope, intercept] = getVSyncPredictionModelLocked();
 
     if (mTimestamps.empty()) {
@@ -299,56 +295,46 @@
     return prediction;
 }
 
-nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
+nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
+                                                     std::optional<nsecs_t> lastVsyncOpt) {
+    ATRACE_CALL();
     std::lock_guard lock(mMutex);
 
-    // update the mLastVsyncSequence for reference point
-    mLastVsyncSequence = getVsyncSequenceLocked(timePoint);
+    const auto now = TimePoint::fromNs(mClock->now());
+    purgeTimelines(now);
 
-    const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int {
-        if (!mRenderRateOpt) return 0;
-        const auto divisor =
-                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
-                                                         *mRenderRateOpt);
-        if (divisor <= 1) return 0;
-
-        int mod = mLastVsyncSequence->seq % divisor;
-        if (mod == 0) return 0;
-
-        // This is actually a bug fix, but guarded with vrr_config since we found it with this
-        // config
-        if (FlagManager::getInstance().vrr_config()) {
-            if (mod < 0) mod += divisor;
-        }
-
-        return divisor - mod;
-    }();
-
-    if (renderRatePhase == 0) {
-        const auto vsyncTime = mLastVsyncSequence->vsyncTime;
-        if (FlagManager::getInstance().vrr_config()) {
-            const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
-            ATRACE_FORMAT("%s InPhase vsyncIn %.2fms", __func__,
-                          ticks<std::milli, float>(vsyncTimePoint - TimePoint::now()));
-            const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
-            mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
-        }
-        return vsyncTime;
+    if (lastVsyncOpt && *lastVsyncOpt > timePoint) {
+        timePoint = *lastVsyncOpt;
     }
 
-    auto const [slope, intercept] = getVSyncPredictionModelLocked();
-    const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
-    const auto nextAnticipatedVsyncTime =
-            nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
-    if (FlagManager::getInstance().vrr_config()) {
-        const auto nextAnticipatedVsyncTimePoint = TimePoint::fromNs(nextAnticipatedVsyncTime);
-        ATRACE_FORMAT("%s outOfPhase vsyncIn %.2fms", __func__,
-                      ticks<std::milli, float>(nextAnticipatedVsyncTimePoint - TimePoint::now()));
-        const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
-        mVsyncTrackerCallback.onVsyncGenerated(nextAnticipatedVsyncTimePoint, mDisplayModePtr,
-                                               renderRate);
+    const auto model = getVSyncPredictionModelLocked();
+    const auto threshold = model.slope / 2;
+    std::optional<Period> minFramePeriodOpt;
+
+    if (mNumVsyncsForFrame > 1) {
+        minFramePeriodOpt = minFramePeriodLocked();
     }
-    return nextAnticipatedVsyncTime;
+
+    std::optional<TimePoint> vsyncOpt;
+    for (auto& timeline : mTimelines) {
+        vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(model, minFramePeriodOpt,
+                                                         snapToVsync(timePoint), mMissedVsync,
+                                                         lastVsyncOpt ? snapToVsync(*lastVsyncOpt -
+                                                                                    threshold)
+                                                                      : lastVsyncOpt);
+        if (vsyncOpt) {
+            break;
+        }
+    }
+    LOG_ALWAYS_FATAL_IF(!vsyncOpt);
+
+    if (*vsyncOpt > mLastCommittedVsync) {
+        mLastCommittedVsync = *vsyncOpt;
+        ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms",
+                              float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f);
+    }
+
+    return vsyncOpt->ns();
 }
 
 /*
@@ -359,39 +345,61 @@
  * isVSyncInPhase(33.3, 30) = false
  * isVSyncInPhase(50.0, 30) = true
  */
-bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const {
-    std::lock_guard lock(mMutex);
-    const auto divisor =
-            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
-                                                     frameRate);
-    return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
-}
-
-bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const {
-    const TimePoint now = TimePoint::now();
-    const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float {
-        return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
-    };
-    ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint),
-                  divisor);
-
-    if (divisor <= 1 || timePoint == 0) {
+bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) {
+    if (timePoint == 0) {
         return true;
     }
 
-    const nsecs_t period = mRateMap[idealPeriod()].slope;
+    std::lock_guard lock(mMutex);
+    const auto model = getVSyncPredictionModelLocked();
+    const nsecs_t period = model.slope;
     const nsecs_t justBeforeTimePoint = timePoint - period / 2;
-    const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint);
-    ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64,
-                          getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq);
-    return vsyncSequence.seq % divisor == 0;
+    const auto now = TimePoint::fromNs(mClock->now());
+    const auto vsync = snapToVsync(justBeforeTimePoint);
+
+    purgeTimelines(now);
+
+    for (auto& timeline : mTimelines) {
+        if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) {
+            return timeline.isVSyncInPhase(model, vsync, frameRate);
+        }
+    }
+
+    // The last timeline should always be valid
+    return mTimelines.back().isVSyncInPhase(model, vsync, frameRate);
 }
 
-void VSyncPredictor::setRenderRate(Fps renderRate) {
+void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) {
     ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
     ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
     std::lock_guard lock(mMutex);
+    const auto prevRenderRate = mRenderRateOpt;
     mRenderRateOpt = renderRate;
+    const auto renderPeriodDelta =
+            prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0;
+    if (applyImmediately) {
+        ATRACE_FORMAT_INSTANT("applyImmediately");
+        while (mTimelines.size() > 1) {
+            mTimelines.pop_front();
+        }
+
+        mTimelines.front().setRenderRate(renderRate);
+        return;
+    }
+
+    const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
+            mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
+    if (newRenderRateIsHigher) {
+        ATRACE_FORMAT_INSTANT("newRenderRateIsHigher");
+        mTimelines.clear();
+        mLastCommittedVsync = TimePoint::fromNs(0);
+
+    } else {
+        mTimelines.back().freeze(
+                TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+    }
+    mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate);
+    purgeTimelines(TimePoint::fromNs(mClock->now()));
 }
 
 void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
@@ -403,10 +411,11 @@
             : std::nullopt;
     ALOGV("%s %s: DisplayMode %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
           to_string(*modePtr).c_str(),
-          timeout ? std::to_string(timeout->notifyExpectedPresentTimeoutNs).c_str() : "N/A");
+          timeout ? std::to_string(timeout->timeoutNs).c_str() : "N/A");
     std::lock_guard lock(mMutex);
 
     mDisplayModePtr = modePtr;
+    mNumVsyncsForFrame = numVsyncsPerFrame(mDisplayModePtr);
     traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs());
 
     static constexpr size_t kSizeLimit = 30;
@@ -418,11 +427,18 @@
         mRateMap[idealPeriod()] = {idealPeriod(), 0};
     }
 
+    mTimelines.clear();
     clearTimestamps();
 }
 
-void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
-                                                  TimePoint lastConfirmedPresentTime) {
+Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
+                                                      TimePoint lastConfirmedPresentTime) {
+    ATRACE_CALL();
+
+    if (mNumVsyncsForFrame <= 1) {
+        return 0ns;
+    }
+
     const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
     const auto threshold = currentPeriod / 2;
     const auto minFramePeriod = minFramePeriodLocked().ns();
@@ -448,16 +464,20 @@
     if (!mPastExpectedPresentTimes.empty()) {
         const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime);
         if (phase > 0ns) {
-            if (mLastVsyncSequence) {
-                mLastVsyncSequence->vsyncTime += phase.ns();
+            for (auto& timeline : mTimelines) {
+                timeline.shiftVsyncSequence(phase);
             }
+            mPastExpectedPresentTimes.clear();
+            return phase;
         }
     }
+
+    return 0ns;
 }
 
 void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime,
                                   TimePoint lastConfirmedPresentTime) {
-    ATRACE_CALL();
+    ATRACE_NAME("VSyncPredictor::onFrameBegin");
     std::lock_guard lock(mMutex);
 
     if (!mDisplayModePtr->getVrrConfig()) return;
@@ -468,23 +488,17 @@
                                                  lastConfirmedPresentTime.ns()) /
                                       1e6f);
     }
-    mPastExpectedPresentTimes.push_back(expectedPresentTime);
-
     const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
     const auto threshold = currentPeriod / 2;
+    mPastExpectedPresentTimes.push_back(expectedPresentTime);
 
-    const auto minFramePeriod = minFramePeriodLocked().ns();
     while (!mPastExpectedPresentTimes.empty()) {
         const auto front = mPastExpectedPresentTimes.front().ns();
-        const bool frontIsLastConfirmed =
-                std::abs(front - lastConfirmedPresentTime.ns()) < threshold;
-        const bool frontIsBeforeConfirmed =
-                front < lastConfirmedPresentTime.ns() - minFramePeriod + threshold;
-        if (frontIsLastConfirmed || frontIsBeforeConfirmed) {
+        const bool frontIsBeforeConfirmed = front < lastConfirmedPresentTime.ns() + threshold;
+        if (frontIsBeforeConfirmed) {
             if (CC_UNLIKELY(mTraceOn)) {
                 ATRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
-                                      static_cast<float>(lastConfirmedPresentTime.ns() -
-                                                         mPastExpectedPresentTimes.front().ns()) /
+                                      static_cast<float>(lastConfirmedPresentTime.ns() - front) /
                                               1e6f);
             }
             mPastExpectedPresentTimes.pop_front();
@@ -493,11 +507,14 @@
         }
     }
 
-    ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+    const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+    if (phase > 0ns) {
+        mMissedVsync = {expectedPresentTime, minFramePeriodLocked()};
+    }
 }
 
 void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) {
-    ATRACE_CALL();
+    ATRACE_NAME("VSyncPredictor::onFrameMissed");
 
     std::lock_guard lock(mMutex);
     if (!mDisplayModePtr->getVrrConfig()) return;
@@ -507,13 +524,15 @@
     const auto lastConfirmedPresentTime =
             TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod);
 
-    ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+    const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+    if (phase > 0ns) {
+        mMissedVsync = {expectedPresentTime, Duration::fromNs(0)};
+    }
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
     std::lock_guard lock(mMutex);
-    const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
-    return {model.slope, model.intercept};
+    return VSyncPredictor::getVSyncPredictionModelLocked();
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
@@ -534,6 +553,24 @@
         mTimestamps.clear();
         mLastTimestampIndex = 0;
     }
+
+    mIdealPeriod = Period::fromNs(idealPeriod());
+    if (mTimelines.empty()) {
+        mLastCommittedVsync = TimePoint::fromNs(0);
+        mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
+    } else {
+        while (mTimelines.size() > 1) {
+            mTimelines.pop_front();
+        }
+        mTimelines.front().setRenderRate(mRenderRateOpt);
+        // set mLastCommittedVsync to a valid vsync but don't commit too much in the future
+        const auto vsyncOpt = mTimelines.front().nextAnticipatedVSyncTimeFrom(
+            getVSyncPredictionModelLocked(),
+            /* minFramePeriodOpt */ std::nullopt,
+            snapToVsync(mClock->now()), MissedVsync{},
+            /* lastVsyncOpt */ std::nullopt);
+        mLastCommittedVsync = *vsyncOpt;
+    }
 }
 
 bool VSyncPredictor::needsMoreSamples() const {
@@ -557,6 +594,167 @@
                       period / 1e6f, periodInterceptTuple.slope / 1e6f,
                       periodInterceptTuple.intercept);
     }
+    StringAppendF(&result, "\tmTimelines.size()=%zu\n", mTimelines.size());
+}
+
+void VSyncPredictor::purgeTimelines(android::TimePoint now) {
+    const auto kEnoughFramesToBreakPhase = 5;
+    if (mRenderRateOpt &&
+        mLastCommittedVsync.ns() + mRenderRateOpt->getPeriodNsecs() * kEnoughFramesToBreakPhase <
+                mClock->now()) {
+        ATRACE_FORMAT_INSTANT("kEnoughFramesToBreakPhase");
+        mTimelines.clear();
+        mLastCommittedVsync = TimePoint::fromNs(0);
+        mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
+        return;
+    }
+
+    while (mTimelines.size() > 1) {
+        const auto validUntilOpt = mTimelines.front().validUntil();
+        if (validUntilOpt && *validUntilOpt < now) {
+            mTimelines.pop_front();
+        } else {
+            break;
+        }
+    }
+    LOG_ALWAYS_FATAL_IF(mTimelines.empty());
+    LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value());
+}
+
+auto VSyncPredictor::VsyncTimeline::makeVsyncSequence(TimePoint knownVsync)
+        -> std::optional<VsyncSequence> {
+    if (knownVsync.ns() == 0) return std::nullopt;
+    return std::make_optional<VsyncSequence>({knownVsync.ns(), 0});
+}
+
+VSyncPredictor::VsyncTimeline::VsyncTimeline(TimePoint knownVsync, Period idealPeriod,
+                                             std::optional<Fps> renderRateOpt)
+      : mIdealPeriod(idealPeriod),
+        mRenderRateOpt(renderRateOpt),
+        mLastVsyncSequence(makeVsyncSequence(knownVsync)) {}
+
+void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) {
+    LOG_ALWAYS_FATAL_IF(mValidUntil.has_value());
+    ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f",
+                          mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA",
+                          float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f);
+    mValidUntil = lastVsync;
+}
+
+std::optional<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom(
+        Model model, std::optional<Period> minFramePeriodOpt, nsecs_t vsync,
+        MissedVsync missedVsync, std::optional<nsecs_t> lastVsyncOpt) {
+    ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
+
+    nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync);
+    const auto threshold = model.slope / 2;
+    const auto lastFrameMissed =
+            lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold;
+    const auto mightBackpressure = minFramePeriodOpt && mRenderRateOpt &&
+            mRenderRateOpt->getPeriod() < 2 * (*minFramePeriodOpt);
+    if (FlagManager::getInstance().vrr_config()) {
+        if (lastFrameMissed) {
+            // If the last frame missed is the last vsync, we already shifted the timeline. Depends
+            // on whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a
+            // different fixup. There is no need to to shift the vsync timeline again.
+            vsyncTime += missedVsync.fixup.ns();
+            ATRACE_FORMAT_INSTANT("lastFrameMissed");
+        } else if (mightBackpressure && lastVsyncOpt) {
+            // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
+            // first before trying to use it.
+            lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+            const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
+            if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
+                // avoid a duplicate vsync
+                ATRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f "
+                                      "which "
+                                      "is %.2f "
+                                      "from "
+                                      "prev. "
+                                      "adjust by %.2f",
+                                      static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
+                                      static_cast<float>(vsyncDiff) / 1e6f,
+                                      static_cast<float>(mRenderRateOpt->getPeriodNsecs()) / 1e6f);
+                vsyncTime += mRenderRateOpt->getPeriodNsecs();
+            }
+        }
+    }
+
+    ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
+    if (mValidUntil && vsyncTime > mValidUntil->ns()) {
+        ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
+                              static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
+        return std::nullopt;
+    }
+
+    return TimePoint::fromNs(vsyncTime);
+}
+
+auto VSyncPredictor::VsyncTimeline::getVsyncSequenceLocked(Model model, nsecs_t vsync)
+        -> VsyncSequence {
+    if (!mLastVsyncSequence) return {vsync, 0};
+
+    const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence;
+    const auto vsyncSequence = lastVsyncSequence +
+            static_cast<int64_t>(std::round((vsync - lastVsyncTime) /
+                                            static_cast<float>(model.slope)));
+    return {vsync, vsyncSequence};
+}
+
+nsecs_t VSyncPredictor::VsyncTimeline::snapToVsyncAlignedWithRenderRate(Model model,
+                                                                        nsecs_t vsync) {
+    // update the mLastVsyncSequence for reference point
+    mLastVsyncSequence = getVsyncSequenceLocked(model, vsync);
+
+    const auto renderRatePhase = [&]() -> int {
+        if (!mRenderRateOpt) return 0;
+        const auto divisor =
+                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod.ns()),
+                                                         *mRenderRateOpt);
+        if (divisor <= 1) return 0;
+
+        int mod = mLastVsyncSequence->seq % divisor;
+        if (mod == 0) return 0;
+
+        // This is actually a bug fix, but guarded with vrr_config since we found it with this
+        // config
+        if (FlagManager::getInstance().vrr_config()) {
+            if (mod < 0) mod += divisor;
+        }
+
+        return divisor - mod;
+    }();
+
+    if (renderRatePhase == 0) {
+        return mLastVsyncSequence->vsyncTime;
+    }
+
+    return mLastVsyncSequence->vsyncTime + model.slope * renderRatePhase;
+}
+
+bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, Fps frameRate) {
+    const auto getVsyncIn = [](TimePoint now, nsecs_t timePoint) -> float {
+        return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
+    };
+
+    Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns());
+    const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate);
+    const auto now = TimePoint::now();
+
+    if (divisor <= 1) {
+        return true;
+    }
+    const auto vsyncSequence = getVsyncSequenceLocked(model, vsync);
+    ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu",
+                          getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor);
+    return vsyncSequence.seq % divisor == 0;
+}
+
+void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) {
+    if (mLastVsyncSequence) {
+        ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
+        mLastVsyncSequence->vsyncTime += phase.ns();
+    }
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 72a3431..8ce61d8 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <scheduler/TimeKeeper.h>
 #include <ui/DisplayId.h>
 
 #include "VSyncTracker.h"
@@ -31,21 +32,22 @@
 class VSyncPredictor : public VSyncTracker {
 public:
     /*
+     * \param [in] Clock The clock abstraction. Useful for unit tests.
      * \param [in] PhysicalDisplayid The display this corresponds to.
      * \param [in] modePtr  The initial display mode
      * \param [in] historySize  The internal amount of entries to store in the model.
      * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before
      * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter
      * samples that fall outlierTolerancePercent from an anticipated vsync event.
-     * \param [in] IVsyncTrackerCallback The callback for the VSyncTracker.
      */
-    VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
-                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
-                   IVsyncTrackerCallback&);
+    VSyncPredictor(std::unique_ptr<Clock>, ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
+                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent);
     ~VSyncPredictor();
 
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final EXCLUDES(mMutex);
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
+                                         std::optional<nsecs_t> lastVsyncOpt = {}) final
+            EXCLUDES(mMutex);
     nsecs_t currentPeriod() const final EXCLUDES(mMutex);
     Period minFramePeriod() const final EXCLUDES(mMutex);
     void resetModel() final EXCLUDES(mMutex);
@@ -62,11 +64,18 @@
 
     VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex);
 
-    bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);
+    bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) final EXCLUDES(mMutex);
 
     void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex);
 
-    void setRenderRate(Fps) final EXCLUDES(mMutex);
+    bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        return mDisplayModePtr->getId() == modePtr->getId() &&
+                mDisplayModePtr->getVsyncRate().getPeriodNsecs() ==
+                mRateMap.find(idealPeriod())->second.slope;
+    }
+
+    void setRenderRate(Fps, bool applyImmediately) final EXCLUDES(mMutex);
 
     void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final
             EXCLUDES(mMutex);
@@ -75,10 +84,44 @@
     void dump(std::string& result) const final EXCLUDES(mMutex);
 
 private:
+    struct VsyncSequence {
+        nsecs_t vsyncTime;
+        int64_t seq;
+    };
+
+    struct MissedVsync {
+        TimePoint vsync = TimePoint::fromNs(0);
+        Duration fixup = Duration::fromNs(0);
+    };
+
+    class VsyncTimeline {
+    public:
+        VsyncTimeline(TimePoint knownVsync, Period idealPeriod, std::optional<Fps> renderRateOpt);
+        std::optional<TimePoint> nextAnticipatedVSyncTimeFrom(
+                Model model, std::optional<Period> minFramePeriodOpt, nsecs_t vsyncTime,
+                MissedVsync lastMissedVsync, std::optional<nsecs_t> lastVsyncOpt = {});
+        void freeze(TimePoint lastVsync);
+        std::optional<TimePoint> validUntil() const { return mValidUntil; }
+        bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate);
+        void shiftVsyncSequence(Duration phase);
+        void setRenderRate(std::optional<Fps> renderRateOpt) { mRenderRateOpt = renderRateOpt; }
+
+    private:
+        nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync);
+        VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync);
+        std::optional<VsyncSequence> makeVsyncSequence(TimePoint knownVsync);
+
+        const Period mIdealPeriod = Duration::fromNs(0);
+        std::optional<Fps> mRenderRateOpt;
+        std::optional<TimePoint> mValidUntil;
+        std::optional<VsyncSequence> mLastVsyncSequence;
+    };
+
     VSyncPredictor(VSyncPredictor const&) = delete;
     VSyncPredictor& operator=(VSyncPredictor const&) = delete;
     void clearTimestamps() REQUIRES(mMutex);
 
+    const std::unique_ptr<Clock> mClock;
     const PhysicalDisplayId mId;
 
     inline void traceInt64If(const char* name, int64_t value) const;
@@ -87,23 +130,17 @@
     size_t next(size_t i) const REQUIRES(mMutex);
     bool validate(nsecs_t timestamp) const REQUIRES(mMutex);
     Model getVSyncPredictionModelLocked() const REQUIRES(mMutex);
-    nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex);
-    bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex);
+    nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex);
     Period minFramePeriodLocked() const REQUIRES(mMutex);
-    void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
+    Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
+    void purgeTimelines(android::TimePoint now) REQUIRES(mMutex);
 
-    struct VsyncSequence {
-        nsecs_t vsyncTime;
-        int64_t seq;
-    };
-    VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex);
     nsecs_t idealPeriod() const REQUIRES(mMutex);
 
     bool const mTraceOn;
     size_t const kHistorySize;
     size_t const kMinimumSamplesForPrediction;
     size_t const kOutlierTolerancePercent;
-    IVsyncTrackerCallback& mVsyncTrackerCallback;
     std::mutex mutable mMutex;
 
     std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
@@ -115,11 +152,16 @@
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
 
     ftl::NonNull<DisplayModePtr> mDisplayModePtr GUARDED_BY(mMutex);
-    std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);
-
-    mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex);
+    int mNumVsyncsForFrame GUARDED_BY(mMutex);
 
     std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex);
+
+    MissedVsync mMissedVsync GUARDED_BY(mMutex);
+
+    std::deque<VsyncTimeline> mTimelines GUARDED_BY(mMutex);
+    TimePoint mLastCommittedVsync GUARDED_BY(mMutex) = TimePoint::fromNs(0);
+    Period mIdealPeriod GUARDED_BY(mMutex) = Duration::fromNs(0);
+    std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 186a2d6..8038364 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -141,8 +141,7 @@
     std::lock_guard lock(mMutex);
     mLastHwVsync.reset();
 
-    if (!mSupportKernelIdleTimer &&
-        modePtr->getVsyncRate().getPeriodNsecs() == mTracker.currentPeriod() && !force) {
+    if (!mSupportKernelIdleTimer && mTracker.isCurrentMode(modePtr) && !force) {
         endPeriodTransition();
         setIgnorePresentFencesInternal(false);
         mMoreSamplesNeeded = false;
@@ -217,6 +216,11 @@
         mMoreSamplesNeeded = mTracker.needsMoreSamples();
     }
 
+    if (mExternalIgnoreFences) {
+      // keep HWVSync on as long as we ignore present fences.
+      mMoreSamplesNeeded = true;
+    }
+
     if (!mMoreSamplesNeeded) {
         setIgnorePresentFencesInternal(false);
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 1ed863c..134d28e 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -26,12 +26,6 @@
 
 namespace android::scheduler {
 
-struct IVsyncTrackerCallback {
-    virtual ~IVsyncTrackerCallback() = default;
-    virtual void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
-                                  Fps renderRate) = 0;
-};
-
 /*
  * VSyncTracker is an interface for providing estimates on future Vsync signal times based on
  * historical vsync timing data.
@@ -57,9 +51,13 @@
      * is updated.
      *
      * \param [in] timePoint    The point in time after which to estimate a vsync event.
+     * \param [in] lastVsyncOpt The last vsync time used by the client. If provided, the tracker
+     *                          should use that as a reference point when generating the new vsync
+     *                          and avoid crossing the minimal frame period of a VRR display.
      * \return                  A prediction of the timestamp of a vsync event.
      */
-    virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const = 0;
+    virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
+                                                 std::optional<nsecs_t> lastVsyncOpt = {}) = 0;
 
     /*
      * The current period of the vsync signal.
@@ -73,6 +71,11 @@
      */
     virtual Period minFramePeriod() const = 0;
 
+    /**
+     * Checks if the sourced mode is equal to the mode in the tracker.
+     */
+    virtual bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const = 0;
+
     /* Inform the tracker that the samples it has are not accurate for prediction. */
     virtual void resetModel() = 0;
 
@@ -84,7 +87,7 @@
      * \param [in] timePoint  A vsync timestamp
      * \param [in] frameRate  The frame rate to check for
      */
-    virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
+    virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) = 0;
 
     /*
      * Sets the active mode of the display which includes the vsync period and other VRR attributes.
@@ -104,8 +107,10 @@
      * when a display is running at 120Hz but the render frame rate is 60Hz.
      *
      * \param [in] Fps   The render rate the tracker should operate at.
+     * \param [in] applyImmediately Whether to apply the new render rate immediately regardless of
+     *                              already committed vsyncs.
      */
-    virtual void setRenderRate(Fps) = 0;
+    virtual void setRenderRate(Fps, bool applyImmediately) = 0;
 
     virtual void onFrameBegin(TimePoint expectedPresentTime,
                               TimePoint lastConfirmedPresentTime) = 0;
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index db6a187..2fa3318 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -57,11 +57,10 @@
 };
 
 VsyncSchedule::VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags features,
-                             RequestHardwareVsync requestHardwareVsync,
-                             IVsyncTrackerCallback& callback)
+                             RequestHardwareVsync requestHardwareVsync)
       : mId(modePtr->getPhysicalDisplayId()),
         mRequestHardwareVsync(std::move(requestHardwareVsync)),
-        mTracker(createTracker(modePtr, callback)),
+        mTracker(createTracker(modePtr)),
         mDispatch(createDispatch(mTracker)),
         mController(createController(modePtr->getPhysicalDisplayId(), *mTracker, features)),
         mTracer(features.test(Feature::kTracePredictedVsync)
@@ -89,8 +88,12 @@
     return period();
 }
 
-TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const {
-    return TimePoint::fromNs(mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns()));
+TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint,
+                                            ftl::Optional<TimePoint> lastVsyncOpt) const {
+    return TimePoint::fromNs(
+            mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns(),
+                                                   lastVsyncOpt.transform(
+                                                           [](TimePoint t) { return t.ns(); })));
 }
 
 void VsyncSchedule::dump(std::string& out) const {
@@ -111,15 +114,14 @@
     mDispatch->dump(out);
 }
 
-VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr,
-                                                       IVsyncTrackerCallback& callback) {
+VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr) {
     // TODO(b/144707443): Tune constants.
     constexpr size_t kHistorySize = 20;
     constexpr size_t kMinSamplesForPrediction = 6;
     constexpr uint32_t kDiscardOutlierPercent = 20;
 
-    return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction,
-                                            kDiscardOutlierPercent, callback);
+    return std::make_unique<VSyncPredictor>(std::make_unique<SystemClock>(), modePtr, kHistorySize,
+                                            kMinSamplesForPrediction, kDiscardOutlierPercent);
 }
 
 VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 722ea0b..881d678 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -57,13 +57,13 @@
 public:
     using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>;
 
-    VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync,
-                  IVsyncTrackerCallback&);
+    VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync);
     ~VsyncSchedule();
 
     // IVsyncSource overrides:
     Period period() const override;
-    TimePoint vsyncDeadlineAfter(TimePoint) const override;
+    TimePoint vsyncDeadlineAfter(TimePoint,
+                                 ftl::Optional<TimePoint> lastVsyncOpt = {}) const override;
     Period minFramePeriod() const override;
 
     // Inform the schedule that the display mode changed the schedule needs to recalibrate
@@ -81,7 +81,7 @@
     bool addResyncSample(TimePoint timestamp, ftl::Optional<Period> hwcVsyncPeriod);
 
     // TODO(b/185535769): Hide behind API.
-    const VsyncTracker& getTracker() const { return *mTracker; }
+    VsyncTracker& getTracker() const { return *mTracker; }
     VsyncTracker& getTracker() { return *mTracker; }
     VsyncController& getController() { return *mController; }
 
@@ -127,7 +127,7 @@
     friend class android::VsyncScheduleTest;
     friend class android::fuzz::SchedulerFuzzer;
 
-    static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr, IVsyncTrackerCallback&);
+    static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr);
     static DispatchPtr createDispatch(TrackerPtr);
     static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags);
 
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Features.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
index 7c72ac6..52485fb 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Features.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
@@ -29,6 +29,7 @@
     kTracePredictedVsync = 1 << 3,
     kBackpressureGpuComposition = 1 << 4,
     kSmallDirtyContentDetection = 1 << 5,
+    kExpectedPresentTime = 1 << 6,
 };
 
 using FeatureFlags = ftl::Flags<Feature>;
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 2806450..84ef89f 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -88,6 +88,7 @@
     NoPreference,
     Low,
     Normal,
+    HighHint,
     High,
 
     ftl_last = High
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
index 59a6df2..f2be316 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -36,8 +36,11 @@
 };
 
 inline std::string to_string(const FrameRateMode& mode) {
-    return to_string(mode.fps) + " (" + to_string(mode.modePtr->getPeakFps()) + "(" +
-            to_string(mode.modePtr->getVsyncRate()) + "))";
+    return base::StringPrintf("{fps=%s, modePtr={id=%d, vsyncRate=%s, peakRefreshRate=%s}}",
+                              to_string(mode.fps).c_str(),
+                              ftl::to_underlying(mode.modePtr->getId()),
+                              to_string(mode.modePtr->getVsyncRate()).c_str(),
+                              to_string(mode.modePtr->getPeakFps()).c_str());
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index 70d4846..d37d2dc 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -19,11 +19,13 @@
 #include <array>
 #include <atomic>
 #include <memory>
+#include <optional>
 
 #include <ui/DisplayId.h>
 #include <ui/Fence.h>
 #include <ui/FenceTime.h>
 
+#include <scheduler/Features.h>
 #include <scheduler/Time.h>
 #include <scheduler/VsyncId.h>
 #include <scheduler/interface/CompositeResult.h>
@@ -31,6 +33,7 @@
 // TODO(b/185536303): Pull to FTL.
 #include "../../../TracedOrdinal.h"
 #include "../../../Utils/Dumper.h"
+#include "../../../Utils/RingBuffer.h"
 
 namespace android::scheduler {
 
@@ -49,39 +52,49 @@
 
     TimePoint expectedPresentTime() const { return mExpectedPresentTime; }
 
+    std::optional<TimePoint> earliestPresentTime() const { return mEarliestPresentTime; }
+
     // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
     TimePoint pastVsyncTime(Period minFramePeriod) const;
 
-    // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
-    TimePoint previousFrameVsyncTime(Period minFramePeriod) const {
-        return mExpectedPresentTime - minFramePeriod;
-    }
-
     // The present fence for the frame that had targeted the most recent VSYNC before this frame.
     // If the target VSYNC for any given frame is more than `vsyncPeriod` in the future, then the
     // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the
     // `presentFenceForPreviousFrame` if running N VSYNCs ahead, but the one that should have been
     // signaled by now (unless that frame missed).
-    const FenceTimePtr& presentFenceForPastVsync(Period minFramePeriod) const;
+    FenceTimePtr presentFenceForPastVsync(Period minFramePeriod) const;
 
     // Equivalent to `presentFenceForPastVsync` unless running N VSYNCs ahead.
     const FenceTimePtr& presentFenceForPreviousFrame() const {
         return mPresentFences.front().fenceTime;
     }
 
-    bool wouldPresentEarly(Period minFramePeriod) const;
-
     bool isFramePending() const { return mFramePending; }
     bool didMissFrame() const { return mFrameMissed; }
     bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; }
+    TimePoint lastSignaledFrameTime() const { return mLastSignaledFrameTime; };
 
 protected:
     explicit FrameTarget(const std::string& displayLabel);
     ~FrameTarget() = default;
 
+    bool wouldPresentEarly(Period minFramePeriod) const;
+
+    // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
+    TimePoint previousFrameVsyncTime(Period minFramePeriod) const {
+        return mExpectedPresentTime - minFramePeriod;
+    }
+
+    void addFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime,
+                  TimePoint expectedPresentTime) {
+        mFenceWithFenceTimes.next() = {std::move(presentFence), presentFenceTime,
+                                       expectedPresentTime};
+    }
+
     VsyncId mVsyncId;
     TimePoint mFrameBeginTime;
     TimePoint mExpectedPresentTime;
+    std::optional<TimePoint> mEarliestPresentTime;
 
     TracedOrdinal<bool> mFramePending;
     TracedOrdinal<bool> mFrameMissed;
@@ -91,23 +104,42 @@
     struct FenceWithFenceTime {
         sp<Fence> fence = Fence::NO_FENCE;
         FenceTimePtr fenceTime = FenceTime::NO_FENCE;
+        TimePoint expectedPresentTime = TimePoint();
     };
     std::array<FenceWithFenceTime, 2> mPresentFences;
+    utils::RingBuffer<FenceWithFenceTime, 5> mFenceWithFenceTimes;
+
+    TimePoint mLastSignaledFrameTime;
 
 private:
+    friend class FrameTargeterTestBase;
+
     template <int N>
     inline bool targetsVsyncsAhead(Period minFramePeriod) const {
         static_assert(N > 1);
         return expectedFrameDuration() > (N - 1) * minFramePeriod;
     }
+
+    const FenceTimePtr pastVsyncTimePtr() const {
+        auto pastFenceTimePtr = FenceTime::NO_FENCE;
+        for (size_t i = 0; i < mFenceWithFenceTimes.size(); i++) {
+            const auto& [_, fenceTimePtr, expectedPresentTime] = mFenceWithFenceTimes[i];
+            if (expectedPresentTime > mFrameBeginTime) {
+                return pastFenceTimePtr;
+            }
+            pastFenceTimePtr = fenceTimePtr;
+        }
+        return pastFenceTimePtr;
+    }
 };
 
 // Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
 class FrameTargeter final : private FrameTarget {
 public:
-    FrameTargeter(PhysicalDisplayId displayId, bool backpressureGpuComposition)
+    FrameTargeter(PhysicalDisplayId displayId, FeatureFlags flags)
           : FrameTarget(to_string(displayId)),
-            mBackpressureGpuComposition(backpressureGpuComposition) {}
+            mBackpressureGpuComposition(flags.test(Feature::kBackpressureGpuComposition)),
+            mSupportsExpectedPresentTime(flags.test(Feature::kExpectedPresentTime)) {}
 
     const FrameTarget& target() const { return *this; }
 
@@ -116,10 +148,14 @@
         VsyncId vsyncId;
         TimePoint expectedVsyncTime;
         Duration sfWorkDuration;
+        Duration hwcMinWorkDuration;
     };
 
     void beginFrame(const BeginFrameArgs&, const IVsyncSource&);
 
+    std::optional<TimePoint> computeEarliestPresentTime(Period minFramePeriod,
+                                                        Duration hwcMinWorkDuration);
+
     // TODO(b/241285191): Merge with FrameTargeter::endFrame.
     FenceTimePtr setPresentFence(sp<Fence>);
 
@@ -128,7 +164,7 @@
     void dump(utils::Dumper&) const;
 
 private:
-    friend class FrameTargeterTest;
+    friend class FrameTargeterTestBase;
 
     // For tests.
     using IsFencePendingFuncPtr = bool (*)(const FenceTimePtr&, int graceTimeMs);
@@ -138,6 +174,7 @@
     static bool isFencePending(const FenceTimePtr&, int graceTimeMs);
 
     const bool mBackpressureGpuComposition;
+    const bool mSupportsExpectedPresentTime;
 
     TimePoint mScheduledPresentTime;
     CompositionCoverageFlags mCompositionCoverage;
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
index 0154060..f0f7a87 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
@@ -16,13 +16,14 @@
 
 #pragma once
 
+#include <ftl/optional.h>
 #include <scheduler/Time.h>
 
 namespace android::scheduler {
 
 struct IVsyncSource {
     virtual Period period() const = 0;
-    virtual TimePoint vsyncDeadlineAfter(TimePoint) const = 0;
+    virtual TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const = 0;
     virtual Period minFramePeriod() const = 0;
 
 protected:
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
index 12ee36e..8673a22 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
@@ -47,6 +47,9 @@
     virtual CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
                                                  const scheduler::FrameTargeters&) = 0;
 
+    // Sends a hint about the expected present time
+    virtual void sendNotifyExpectedPresentHint(PhysicalDisplayId) = 0;
+
     // Samples the composited frame via RegionSamplingThread.
     virtual void sample() = 0;
 
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index e80372b..badd21e 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -16,6 +16,7 @@
 
 #include <gui/TraceUtils.h>
 
+#include <common/FlagManager.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/IVsyncSource.h>
 
@@ -33,8 +34,10 @@
     return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift);
 }
 
-const FenceTimePtr& FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
-    // TODO(b/267315508): Generalize to N VSYNCs.
+FenceTimePtr FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
+    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        return pastVsyncTimePtr();
+    }
     const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
     return mPresentFences[i].fenceTime;
 }
@@ -44,7 +47,8 @@
     // should use `TimePoint::now()` in case of delays since `mFrameBeginTime`.
 
     // TODO(b/267315508): Generalize to N VSYNCs.
-    if (targetsVsyncsAhead<3>(minFramePeriod)) {
+    const bool allowNVsyncs = FlagManager::getInstance().allow_n_vsyncs_in_targeter();
+    if (!allowNVsyncs && targetsVsyncsAhead<3>(minFramePeriod)) {
         return true;
     }
 
@@ -82,6 +86,10 @@
         }
     }
 
+    if (!mSupportsExpectedPresentTime) {
+        mEarliestPresentTime = computeEarliestPresentTime(minFramePeriod, args.hwcMinWorkDuration);
+    }
+
     ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
                   ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
                   mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
@@ -109,6 +117,7 @@
     mFrameMissed = mFramePending || [&] {
         const nsecs_t pastPresentTime = pastPresentFence->getSignalTime();
         if (pastPresentTime < 0) return false;
+        mLastSignaledFrameTime = TimePoint::fromNs(pastPresentTime);
         const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2;
         return lastScheduledPresentTime.ns() < pastPresentTime - frameMissedSlop;
     }();
@@ -121,6 +130,14 @@
     if (mGpuFrameMissed) mGpuFrameMissedCount++;
 }
 
+std::optional<TimePoint> FrameTargeter::computeEarliestPresentTime(Period minFramePeriod,
+                                                                   Duration hwcMinWorkDuration) {
+    if (wouldPresentEarly(minFramePeriod)) {
+        return previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration;
+    }
+    return {};
+}
+
 void FrameTargeter::endFrame(const CompositeResult& result) {
     mCompositionCoverage = result.compositionCoverage;
 }
@@ -131,8 +148,12 @@
 }
 
 FenceTimePtr FrameTargeter::setPresentFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime) {
-    mPresentFences[1] = mPresentFences[0];
-    mPresentFences[0] = {std::move(presentFence), presentFenceTime};
+    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        addFence(std::move(presentFence), presentFenceTime, mExpectedPresentTime);
+    } else {
+        mPresentFences[1] = mPresentFences[0];
+        mPresentFences[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime};
+    }
     return presentFenceTime;
 }
 
diff --git a/services/surfaceflinger/Scheduler/src/Timer.cpp b/services/surfaceflinger/Scheduler/src/Timer.cpp
index 09e8a1e..eeb9c60 100644
--- a/services/surfaceflinger/Scheduler/src/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/src/Timer.cpp
@@ -159,7 +159,7 @@
         ALOGW("Failed to set SCHED_FIFO on dispatch thread");
     }
 
-    if (pthread_setname_np(pthread_self(), "TimerDispatch")) {
+    if (pthread_setname_np(pthread_self(), "TimerDispatch") != 0) {
         ALOGW("Failed to set thread name on dispatch thread");
     }
 
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index c883385..5448eec 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -24,6 +24,7 @@
 
 #include <com_android_graphics_surfaceflinger_flags.h>
 
+using namespace com::android::graphics::surfaceflinger;
 using namespace std::chrono_literals;
 
 namespace android::scheduler {
@@ -38,18 +39,26 @@
     const TimePoint vsyncDeadline;
 
     Period period() const override { return vsyncPeriod; }
-    TimePoint vsyncDeadlineAfter(TimePoint) const override { return vsyncDeadline; }
+    TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const override {
+        return vsyncDeadline;
+    }
     Period minFramePeriod() const override { return framePeriod; }
 };
 
 } // namespace
 
-class FrameTargeterTest : public testing::Test {
+class FrameTargeterTestBase : public testing::Test {
 public:
+    FrameTargeterTestBase(FeatureFlags flags) : mTargeter(PhysicalDisplayId::fromPort(13), flags) {}
+
     const auto& target() const { return mTargeter.target(); }
 
+    bool wouldPresentEarly(Period minFramePeriod) const {
+        return target().wouldPresentEarly(minFramePeriod);
+    }
+
     struct Frame {
-        Frame(FrameTargeterTest* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime,
+        Frame(FrameTargeterTestBase* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime,
               Duration frameDuration, Fps refreshRate, Fps peakRefreshRate,
               FrameTargeter::IsFencePendingFuncPtr isFencePendingFuncPtr = Frame::fenceSignaled,
               const ftl::Optional<VsyncSource>& vsyncSourceOpt = std::nullopt)
@@ -61,7 +70,8 @@
                                                      .vsyncId = vsyncId,
                                                      .expectedVsyncTime =
                                                              frameBeginTime + frameDuration,
-                                                     .sfWorkDuration = 10ms};
+                                                     .sfWorkDuration = 10ms,
+                                                     .hwcMinWorkDuration = kHwcMinWorkDuration};
 
             testPtr->mTargeter.beginFrame(args,
                                           vsyncSourceOpt
@@ -93,7 +103,7 @@
         static bool fencePending(const FenceTimePtr&, int) { return true; }
         static bool fenceSignaled(const FenceTimePtr&, int) { return false; }
 
-        FrameTargeterTest* const testPtr;
+        FrameTargeterTestBase* const testPtr;
 
         TimePoint& frameBeginTime;
         const Period period;
@@ -102,11 +112,24 @@
         bool ended = false;
     };
 
+    static constexpr Duration kHwcMinWorkDuration = std::chrono::nanoseconds(5ns);
+
 private:
     FenceToFenceTimeMap mFenceMap;
 
-    static constexpr bool kBackpressureGpuComposition = true;
-    FrameTargeter mTargeter{PhysicalDisplayId::fromPort(13), kBackpressureGpuComposition};
+    FrameTargeter mTargeter;
+};
+
+class FrameTargeterTest : public FrameTargeterTestBase {
+public:
+    FrameTargeterTest() : FrameTargeterTestBase(Feature::kBackpressureGpuComposition) {}
+};
+
+class FrameTargeterWithExpectedPresentSupportTest : public FrameTargeterTestBase {
+public:
+    FrameTargeterWithExpectedPresentSupportTest()
+          : FrameTargeterTestBase(FeatureFlags(Feature::kBackpressureGpuComposition) |
+                                  Feature::kExpectedPresentTime) {}
 };
 
 TEST_F(FrameTargeterTest, targetsFrames) {
@@ -146,6 +169,7 @@
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsync) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{111};
     TimePoint frameBeginTime(1000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -162,6 +186,7 @@
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
     constexpr Fps kRefreshRate = 120_Hz;
@@ -181,8 +206,32 @@
     }
 }
 
+TEST_F(FrameTargeterTest, recallsPastNVsyncTwoVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+    VsyncId vsyncId{222};
+    TimePoint frameBeginTime(2000ms);
+    constexpr Fps kRefreshRate = 120_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 10ms;
+
+    FenceTimePtr previousFence = FenceTime::NO_FENCE;
+
+    for (int n = 5; n-- > 0;) {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto fence = frame.end();
+
+        const auto pastVsyncTime = frameBeginTime + kFrameDuration - 2 * kPeriod;
+        EXPECT_EQ(target().pastVsyncTime(kPeriod), pastVsyncTime);
+        EXPECT_EQ(target().presentFenceForPastVsync(kFrameDuration), previousFence);
+
+        frameBeginTime += kPeriod;
+        previousFence = fence;
+    }
+}
+
 TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) {
-    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
 
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
@@ -205,13 +254,41 @@
     }
 }
 
+TEST_F(FrameTargeterTest, recallsPastNVsyncTwoVsyncsAheadVrr) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+
+    VsyncId vsyncId{222};
+    TimePoint frameBeginTime(2000ms);
+    constexpr Fps kRefreshRate = 120_Hz;
+    constexpr Fps kPeakRefreshRate = 240_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 10ms;
+
+    FenceTimePtr previousFence = FenceTime::NO_FENCE;
+
+    for (int n = 5; n-- > 0;) {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
+                    kPeakRefreshRate);
+        const auto fence = frame.end();
+
+        const auto pastVsyncTime = frameBeginTime + kFrameDuration - 2 * kPeriod;
+        EXPECT_EQ(target().pastVsyncTime(kPeriod), pastVsyncTime);
+        EXPECT_EQ(target().presentFenceForPastVsync(kFrameDuration), previousFence);
+
+        frameBeginTime += kPeriod;
+        previousFence = fence;
+    }
+}
+
 TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) {
     constexpr Period kPeriod = (60_Hz).getPeriod();
     EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), FenceTime::NO_FENCE);
-    EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+    EXPECT_FALSE(wouldPresentEarly(kPeriod));
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresent) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{333};
     TimePoint frameBeginTime(3000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -220,7 +297,8 @@
     // The target is not early while past present fences are pending.
     for (int n = 3; n-- > 0;) {
         const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-        EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        EXPECT_FALSE(target().earliestPresentTime());
     }
 
     // The target is early if the past present fence was signaled.
@@ -228,10 +306,46 @@
     const auto fence = frame.end();
     fence->signalForTest(frameBeginTime.ns());
 
-    EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+    Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
+    // `finalFrame` would present early, so it has an earliest present time.
+    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    ASSERT_NE(std::nullopt, target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
+}
+
+// Same as `detectsEarlyPresent`, above, but verifies that we do not set an earliest present time
+// when there is expected present time support.
+TEST_F(FrameTargeterWithExpectedPresentSupportTest, detectsEarlyPresent) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
+    VsyncId vsyncId{333};
+    TimePoint frameBeginTime(3000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+    // The target is not early while past present fences are pending.
+    for (int n = 3; n-- > 0;) {
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        EXPECT_FALSE(target().earliestPresentTime());
+    }
+
+    // The target is early if the past present fence was signaled.
+    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+    const auto fence = frame.end();
+    fence->signalForTest(frameBeginTime.ns());
+
+    Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
+    // `finalFrame` would present early, but we have expected present time support, so it has no
+    // earliest present time.
+    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    ASSERT_EQ(std::nullopt, target().earliestPresentTime());
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{444};
     TimePoint frameBeginTime(4000ms);
     constexpr Fps kRefreshRate = 120_Hz;
@@ -240,7 +354,8 @@
     // The target is not early while past present fences are pending.
     for (int n = 3; n-- > 0;) {
         const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-        EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        EXPECT_FALSE(target().earliestPresentTime());
     }
 
     Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
@@ -248,15 +363,66 @@
     fence->signalForTest(frameBeginTime.ns());
 
     // The target is two VSYNCs ahead, so the past present fence is still pending.
-    EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+    EXPECT_FALSE(wouldPresentEarly(kPeriod));
+    EXPECT_FALSE(target().earliestPresentTime());
 
     { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); }
 
+    Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
     // The target is early if the past present fence was signaled.
-    EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    ASSERT_NE(std::nullopt, target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresentNVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+    VsyncId vsyncId{444};
+    TimePoint frameBeginTime(4000ms);
+    Fps refreshRate = 120_Hz;
+    Period period = refreshRate.getPeriod();
+
+    // The target is not early while past present fences are pending.
+    for (int n = 5; n-- > 0;) {
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
+        EXPECT_FALSE(wouldPresentEarly(period));
+        EXPECT_FALSE(target().earliestPresentTime());
+    }
+
+    Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
+    auto fence = frame.end();
+    frameBeginTime += period;
+    fence->signalForTest(frameBeginTime.ns());
+
+    // The target is two VSYNCs ahead, so the past present fence is still pending.
+    EXPECT_FALSE(wouldPresentEarly(period));
+    EXPECT_FALSE(target().earliestPresentTime());
+
+    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate); }
+
+    Frame oneEarlyPresentFrame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
+    // The target is early if the past present fence was signaled.
+    EXPECT_TRUE(wouldPresentEarly(period));
+    ASSERT_NE(std::nullopt, target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - period - kHwcMinWorkDuration);
+
+    fence = oneEarlyPresentFrame.end();
+    frameBeginTime += period;
+    fence->signalForTest(frameBeginTime.ns());
+
+    // Change rate to track frame more than 2 vsyncs ahead
+    refreshRate = 144_Hz;
+    period = refreshRate.getPeriod();
+    Frame onePresentEarlyFrame(this, vsyncId++, frameBeginTime, 16ms, refreshRate, refreshRate);
+    // The target is not early as last frame as the past frame is tracked for pending.
+    EXPECT_FALSE(wouldPresentEarly(period));
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     TimePoint frameBeginTime(5000ms);
     constexpr Fps kRefreshRate = 144_Hz;
     constexpr Period kPeriod = kRefreshRate.getPeriod();
@@ -264,7 +430,10 @@
     const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate);
 
     // The target is more than two VSYNCs ahead, but present fences are not tracked that far back.
-    EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
 }
 
 TEST_F(FrameTargeterTest, detectsMissedFrames) {
diff --git a/services/surfaceflinger/StartPropertySetThread.cpp b/services/surfaceflinger/StartPropertySetThread.cpp
deleted file mode 100644
index f42cd53..0000000
--- a/services/surfaceflinger/StartPropertySetThread.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 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 <cutils/properties.h>
-#include "StartPropertySetThread.h"
-
-namespace android {
-
-StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
-        Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}
-
-status_t StartPropertySetThread::Start() {
-    return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
-}
-
-bool StartPropertySetThread::threadLoop() {
-    // Set property service.sf.present_timestamp, consumer need check its readiness
-    property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
-    // Clear BootAnimation exit flag
-    property_set("service.bootanim.exit", "0");
-    property_set("service.bootanim.progress", "0");
-    // Start BootAnimation if not started
-    property_set("ctl.start", "bootanim");
-    // Exit immediately
-    return false;
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/StartPropertySetThread.h b/services/surfaceflinger/StartPropertySetThread.h
deleted file mode 100644
index bbdcde2..0000000
--- a/services/surfaceflinger/StartPropertySetThread.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 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_STARTBOOTANIMTHREAD_H
-#define ANDROID_STARTBOOTANIMTHREAD_H
-
-#include <stddef.h>
-
-#include <utils/Mutex.h>
-#include <utils/Thread.h>
-
-namespace android {
-
-class StartPropertySetThread : public Thread {
-// Boot animation is triggered via calls to "property_set()" which can block
-// if init's executing slow operation such as 'mount_all --late' (currently
-// happening 1/10th with fsck)  concurrently. Running in a separate thread
-// allows to pursue the SurfaceFlinger's init process without blocking.
-// see b/34499826.
-// Any property_set() will block during init stage so need to be offloaded
-// to this thread. see b/63844978.
-public:
-    explicit StartPropertySetThread(bool timestampPropertyValue);
-    status_t Start();
-private:
-    virtual bool threadLoop();
-    static constexpr const char* kTimestampProperty = "service.sf.present_timestamp";
-    const bool mTimestampPropertyValue;
-};
-
-}
-
-#endif // ANDROID_STARTBOOTANIMTHREAD_H
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b980a65..b8b7a70 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -40,6 +40,7 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
+#include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/Display.h>
@@ -55,6 +56,7 @@
 #include <configstore/Utils.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
+#include <fmt/format.h>
 #include <ftl/algorithm.h>
 #include <ftl/concat.h>
 #include <ftl/fake_guard.h>
@@ -64,7 +66,6 @@
 #include <gui/BufferQueue.h>
 #include <gui/DebugEGLImageTracker.h>
 #include <gui/IProducerListener.h>
-#include <gui/LayerDebugInfo.h>
 #include <gui/LayerMetadata.h>
 #include <gui/LayerState.h>
 #include <gui/Surface.h>
@@ -117,6 +118,7 @@
 #include <common/FlagManager.h>
 #include <gui/LayerStatePermissions.h>
 #include <gui/SchedulingPolicy.h>
+#include <gui/SyncScreenCaptureListener.h>
 #include <ui/DisplayIdentification.h>
 #include "BackgroundExecutor.h"
 #include "Client.h"
@@ -147,13 +149,13 @@
 #include "MutexUtils.h"
 #include "NativeWindowSurface.h"
 #include "RegionSamplingThread.h"
+#include "RenderAreaBuilder.h"
 #include "Scheduler/EventThread.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VsyncConfiguration.h"
 #include "Scheduler/VsyncModulator.h"
 #include "ScreenCaptureOutput.h"
-#include "StartPropertySetThread.h"
 #include "SurfaceFlingerProperties.h"
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
@@ -168,10 +170,6 @@
 #define NO_THREAD_SAFETY_ANALYSIS \
     _Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
 
-// To enable layer borders in the system, change the below flag to true.
-#undef DOES_CONTAIN_BORDER
-#define DOES_CONTAIN_BORDER false
-
 namespace android {
 using namespace std::chrono_literals;
 using namespace std::string_literals;
@@ -232,32 +230,26 @@
     return dataspace == Dataspace::V0_SRGB || dataspace == Dataspace::DISPLAY_P3;
 }
 
-std::chrono::milliseconds getIdleTimerTimeout(DisplayId displayId) {
-    const auto displayIdleTimerMsKey = [displayId] {
-        std::stringstream ss;
-        ss << "debug.sf.set_idle_timer_ms_" << displayId.value;
-        return ss.str();
-    }();
-
-    const int32_t displayIdleTimerMs = base::GetIntProperty(displayIdleTimerMsKey, 0);
-    if (displayIdleTimerMs > 0) {
+std::chrono::milliseconds getIdleTimerTimeout(PhysicalDisplayId displayId) {
+    if (const int32_t displayIdleTimerMs =
+                base::GetIntProperty("debug.sf.set_idle_timer_ms_"s +
+                                             std::to_string(displayId.value),
+                                     0);
+        displayIdleTimerMs > 0) {
         return std::chrono::milliseconds(displayIdleTimerMs);
     }
 
-    const int32_t setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms", 0);
+    const int32_t setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms"s, 0);
     const int32_t millis = setIdleTimerMs ? setIdleTimerMs : sysprop::set_idle_timer_ms(0);
     return std::chrono::milliseconds(millis);
 }
 
-bool getKernelIdleTimerSyspropConfig(DisplayId displayId) {
-    const auto displaySupportKernelIdleTimerKey = [displayId] {
-        std::stringstream ss;
-        ss << "debug.sf.support_kernel_idle_timer_" << displayId.value;
-        return ss.str();
-    }();
+bool getKernelIdleTimerSyspropConfig(PhysicalDisplayId displayId) {
+    const bool displaySupportKernelIdleTimer =
+            base::GetBoolProperty("debug.sf.support_kernel_idle_timer_"s +
+                                          std::to_string(displayId.value),
+                                  false);
 
-    const auto displaySupportKernelIdleTimer =
-            base::GetBoolProperty(displaySupportKernelIdleTimerKey, false);
     return displaySupportKernelIdleTimer || sysprop::support_kernel_idle_timer(false);
 }
 
@@ -434,7 +426,8 @@
         mInternalDisplayDensity(
                 getDensityFromProperty("ro.sf.lcd_density", !mEmulatedDisplayDensity)),
         mPowerAdvisor(std::make_unique<Hwc2::impl::PowerAdvisor>(*this)),
-        mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make()) {
+        mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make()),
+        mSkipPowerOnForQuiescent(base::GetBoolProperty("ro.boot.quiescent"s, false)) {
     ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str());
 }
 
@@ -467,11 +460,10 @@
     wideColorGamutCompositionPixelFormat =
             static_cast<ui::PixelFormat>(wcg_composition_pixel_format(ui::PixelFormat::RGBA_8888));
 
-    mLayerCachingEnabled = [] {
-        const bool enable =
-                android::sysprop::SurfaceFlingerProperties::enable_layer_caching().value_or(false);
-        return base::GetBoolProperty(std::string("debug.sf.enable_layer_caching"), enable);
-    }();
+    mLayerCachingEnabled =
+            base::GetBoolProperty("debug.sf.enable_layer_caching"s,
+                                  sysprop::SurfaceFlingerProperties::enable_layer_caching()
+                                          .value_or(false));
 
     useContextPriority = use_context_priority(true);
 
@@ -493,14 +485,6 @@
     mSupportsBlur = supportsBlurs;
     ALOGI_IF(!mSupportsBlur, "Disabling blur effects, they are not supported.");
 
-    const size_t defaultListSize = MAX_LAYERS;
-    auto listSize = property_get_int32("debug.sf.max_igbp_list_size", int32_t(defaultListSize));
-    mMaxGraphicBufferProducerListSize = (listSize > 0) ? size_t(listSize) : defaultListSize;
-    mGraphicBufferProducerListSizeLogThreshold =
-            std::max(static_cast<int>(0.95 *
-                                      static_cast<double>(mMaxGraphicBufferProducerListSize)),
-                     1);
-
     property_get("debug.sf.luma_sampling", value, "1");
     mLumaSampling = atoi(value);
 
@@ -550,8 +534,12 @@
 
     mLayerLifecycleManagerEnabled =
             base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true);
-    mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
-            base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
+
+    // These are set by the HWC implementation to indicate that they will use the workarounds.
+    mIsHotplugErrViaNegVsync =
+            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
+
+    mIsHdcpViaNegVsync = base::GetBoolProperty("debug.sf.hwc_hdcp_via_neg_vsync"s, false);
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -575,20 +563,25 @@
         initializeDisplays();
     }));
 
-    startBootAnim();
+    mInitBootPropsFuture.callOnce([this] {
+        return std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this);
+    });
+
+    mInitBootPropsFuture.wait();
 }
 
 void SurfaceFlinger::run() {
     mScheduler->run();
 }
 
-sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure,
-                                          float requestedRefreshRate) {
-    // onTransact already checks for some permissions, but adding an additional check here.
-    // This is to ensure that only system and graphics can request to create a secure
+sp<IBinder> SurfaceFlinger::createVirtualDisplay(const std::string& displayName, bool isSecure,
+                                                 const std::string& uniqueId,
+                                                 float requestedRefreshRate) {
+    // SurfaceComposerAIDL checks for some permissions, but adding an additional check here.
+    // This is to ensure that only root, system, and graphics can request to create a secure
     // display. Secure displays can show secure content so we add an additional restriction on it.
-    const int uid = IPCThreadState::self()->getCallingUid();
-    if (secure && uid != AID_GRAPHICS && uid != AID_SYSTEM) {
+    const uid_t uid = IPCThreadState::self()->getCallingUid();
+    if (isSecure && uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM) {
         ALOGE("Only privileged processes can create a secure display");
         return nullptr;
     }
@@ -612,32 +605,34 @@
     Mutex::Autolock _l(mStateLock);
     // Display ID is assigned when virtual display is allocated by HWC.
     DisplayDeviceState state;
-    state.isSecure = secure;
+    state.isSecure = isSecure;
     // Set display as protected when marked as secure to ensure no behavior change
     // TODO (b/314820005): separate as a different arg when creating the display.
-    state.isProtected = secure;
+    state.isProtected = isSecure;
     state.displayName = displayName;
+    state.uniqueId = uniqueId;
     state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
     mCurrentState.displays.add(token, state);
     return token;
 }
 
-void SurfaceFlinger::destroyDisplay(const sp<IBinder>& displayToken) {
+status_t SurfaceFlinger::destroyVirtualDisplay(const sp<IBinder>& displayToken) {
     Mutex::Autolock lock(mStateLock);
 
     const ssize_t index = mCurrentState.displays.indexOfKey(displayToken);
     if (index < 0) {
         ALOGE("%s: Invalid display token %p", __func__, displayToken.get());
-        return;
+        return NAME_NOT_FOUND;
     }
 
     const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
     if (state.physical) {
         ALOGE("%s: Invalid operation on physical display", __func__);
-        return;
+        return INVALID_OPERATION;
     }
     mCurrentState.displays.removeItemsAt(index);
     setTransactionFlags(eDisplayTransactionNeeded);
+    return NO_ERROR;
 }
 
 void SurfaceFlinger::enableHalVirtualDisplays(bool enable) {
@@ -731,13 +726,10 @@
     }
     mBootFinished = true;
     FlagManager::getMutableInstance().markBootCompleted();
-    if (mStartPropertySetThread->join() != NO_ERROR) {
-        ALOGE("Join StartPropertySetThread failed!");
-    }
 
-    if (mRenderEnginePrimeCacheFuture.valid()) {
-        mRenderEnginePrimeCacheFuture.get();
-    }
+    mInitBootPropsFuture.wait();
+    mRenderEnginePrimeCacheFuture.wait();
+
     const nsecs_t now = systemTime();
     const nsecs_t duration = now - mBootTime;
     ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
@@ -764,7 +756,7 @@
 
     sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger")));
 
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
         if (input == nullptr) {
             ALOGE("Failed to link to input service");
         } else {
@@ -801,27 +793,55 @@
     }));
 }
 
-static std::optional<renderengine::RenderEngine::RenderEngineType>
-chooseRenderEngineTypeViaSysProp() {
+void chooseRenderEngineType(renderengine::RenderEngineCreationArgs::Builder& builder) {
     char prop[PROPERTY_VALUE_MAX];
-    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "skiaglthreaded");
+    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
 
+    // TODO: b/293371537 - Once GraphiteVk is deemed relatively stable, log a warning that
+    // PROPERTY_DEBUG_RENDERENGINE_BACKEND is deprecated
     if (strcmp(prop, "skiagl") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
+        builder.setThreaded(renderengine::RenderEngine::Threaded::NO)
+                .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::GL);
     } else if (strcmp(prop, "skiaglthreaded") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED;
+        builder.setThreaded(renderengine::RenderEngine::Threaded::YES)
+                .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::GL);
     } else if (strcmp(prop, "skiavk") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_VK;
+        builder.setThreaded(renderengine::RenderEngine::Threaded::NO)
+                .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK);
     } else if (strcmp(prop, "skiavkthreaded") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_VK_THREADED;
+        builder.setThreaded(renderengine::RenderEngine::Threaded::YES)
+                .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK);
     } else {
-        ALOGE("Unrecognized RenderEngineType %s; ignoring!", prop);
-        return {};
+        const auto kVulkan = renderengine::RenderEngine::GraphicsApi::VK;
+        const bool useGraphite = FlagManager::getInstance().graphite_renderengine() &&
+                renderengine::RenderEngine::canSupport(kVulkan);
+        const bool useVulkan = useGraphite ||
+                (FlagManager::getInstance().vulkan_renderengine() &&
+                 renderengine::RenderEngine::canSupport(kVulkan));
+
+        builder.setSkiaBackend(useGraphite ? renderengine::RenderEngine::SkiaBackend::GRAPHITE
+                                           : renderengine::RenderEngine::SkiaBackend::GANESH);
+        builder.setGraphicsApi(useVulkan ? kVulkan : renderengine::RenderEngine::GraphicsApi::GL);
     }
 }
 
-// Do not call property_set on main thread which will be blocked by init
-// Use StartPropertySetThread instead.
+/**
+ * Choose a suggested blurring algorithm if supportsBlur is true. By default Kawase will be
+ * suggested as it's faster than a full Gaussian blur and looks close enough.
+ */
+renderengine::RenderEngine::BlurAlgorithm chooseBlurAlgorithm(bool supportsBlur) {
+    if (!supportsBlur) {
+        return renderengine::RenderEngine::BlurAlgorithm::NONE;
+    }
+
+    auto const algorithm = base::GetProperty(PROPERTY_DEBUG_RENDERENGINE_BLUR_ALGORITHM, "");
+    if (algorithm == "gaussian") {
+        return renderengine::RenderEngine::BlurAlgorithm::GAUSSIAN;
+    } else {
+        return renderengine::RenderEngine::BlurAlgorithm::KAWASE;
+    }
+}
+
 void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) {
     ATRACE_CALL();
     ALOGI(  "SurfaceFlinger's main thread ready to run. "
@@ -837,14 +857,12 @@
                            .setImageCacheSize(maxFrameBufferAcquiredBuffers)
                            .setEnableProtectedContext(enable_protected_contents(false))
                            .setPrecacheToneMapperShaderOnly(false)
-                           .setSupportsBackgroundBlur(mSupportsBlur)
+                           .setBlurAlgorithm(chooseBlurAlgorithm(mSupportsBlur))
                            .setContextPriority(
                                    useContextPriority
                                            ? renderengine::RenderEngine::ContextPriority::REALTIME
                                            : renderengine::RenderEngine::ContextPriority::MEDIUM);
-    if (auto type = chooseRenderEngineTypeViaSysProp()) {
-        builder.setRenderEngineType(type.value());
-    }
+    chooseRenderEngineType(builder);
     mRenderEngine = renderengine::RenderEngine::create(builder.build());
     mCompositionEngine->setRenderEngine(mRenderEngine.get());
     mMaxRenderTargetSize =
@@ -860,6 +878,9 @@
     mCompositionEngine->getHwComposer().setCallback(*this);
     ClientCache::getInstance().setRenderEngine(&getRenderEngine());
 
+    mHasReliablePresentFences =
+            !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
+
     enableLatchUnsignaledConfig = getLatchUnsignaledConfig();
 
     if (base::GetBoolProperty("debug.sf.enable_hwc_vds"s, false)) {
@@ -892,7 +913,6 @@
     // the DisplayDevice, hence the above commit of the primary display. Remove that special case by
     // initializing the Scheduler after configureLocked, once decoupled from DisplayDevice.
     initScheduler(display);
-    dispatchDisplayHotplugEvent(display->getPhysicalId(), true);
 
     mLayerTracing.setTakeLayersSnapshotProtoFunction([&](uint32_t traceFlags) {
         auto snapshot = perfetto::protos::LayersSnapshotProto{};
@@ -923,29 +943,65 @@
             ALOGW("Can't set SCHED_OTHER for primeCache");
         }
 
-        bool shouldPrimeUltraHDR =
-                base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
-        mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
+        mRenderEnginePrimeCacheFuture.callOnce([this] {
+            renderengine::PrimeCacheConfig config;
+            config.cacheHolePunchLayer =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.hole_punch"s, true);
+            config.cacheSolidLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.solid_layers"s, true);
+            config.cacheSolidDimmedLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.solid_dimmed_layers"s, true);
+            config.cacheImageLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.image_layers"s, true);
+            config.cacheImageDimmedLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.image_dimmed_layers"s, true);
+            config.cacheClippedLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.clipped_layers"s, true);
+            config.cacheShadowLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.shadow_layers"s, true);
+            config.cachePIPImageLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.pip_image_layers"s, true);
+            config.cacheTransparentImageDimmedLayers = base::
+                    GetBoolProperty("debug.sf.prime_shader_cache.transparent_image_dimmed_layers"s,
+                                    true);
+            config.cacheClippedDimmedImageLayers = base::
+                    GetBoolProperty("debug.sf.prime_shader_cache.clipped_dimmed_image_layers"s,
+                                    true);
+            // ro.surface_flinger.prime_chader_cache.ultrahdr exists as a previous ro property
+            // which we maintain for backwards compatibility.
+            config.cacheUltraHDR =
+                    base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
+            return getRenderEngine().primeCache(config);
+        });
 
         if (setSchedFifo(true) != NO_ERROR) {
             ALOGW("Can't set SCHED_FIFO after primeCache");
         }
     }
 
-    // Inform native graphics APIs whether the present timestamp is supported:
-
-    const bool presentFenceReliable =
-            !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
-    mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);
-
-    if (mStartPropertySetThread->Start() != NO_ERROR) {
-        ALOGE("Run StartPropertySetThread failed!");
-    }
+    // Avoid blocking the main thread on `init` to set properties.
+    mInitBootPropsFuture.callOnce([this] {
+        return std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this);
+    });
 
     initTransactionTraceWriter();
     ALOGV("Done initializing");
 }
 
+// During boot, offload `initBootProperties` to another thread. `property_set` depends on
+// `property_service`, which may be delayed by slow operations like `mount_all --late` in
+// the `init` process. See b/34499826 and b/63844978.
+void SurfaceFlinger::initBootProperties() {
+    property_set("service.sf.present_timestamp", mHasReliablePresentFences ? "1" : "0");
+
+    if (base::GetBoolProperty("debug.sf.boot_animation"s, true)) {
+        // Reset and (if needed) start BootAnimation.
+        property_set("service.bootanim.exit", "0");
+        property_set("service.bootanim.progress", "0");
+        property_set("ctl.start", "bootanim");
+    }
+}
+
 void SurfaceFlinger::initTransactionTraceWriter() {
     if (!mTransactionTracing) {
         return;
@@ -957,8 +1013,9 @@
                         ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str());
                         return;
                     }
-                    mTransactionTracing->flush();
+                    ALOGD("TransactionTraceWriter: writing file=%s", filename.c_str());
                     mTransactionTracing->writeToFile(filename);
+                    mTransactionTracing->flush();
                 };
                 if (std::this_thread::get_id() == mMainThreadId) {
                     writeFn();
@@ -985,18 +1042,6 @@
             static_cast<ui::ColorMode>(base::GetIntProperty("persist.sys.sf.color_mode"s, 0));
 }
 
-void SurfaceFlinger::startBootAnim() {
-    // Start boot animation service by setting a property mailbox
-    // if property setting thread is already running, Start() will be just a NOP
-    mStartPropertySetThread->Start();
-    // Wait until property was set
-    if (mStartPropertySetThread->join() != NO_ERROR) {
-        ALOGE("Join StartPropertySetThread failed!");
-    }
-}
-
-// ----------------------------------------------------------------------------
-
 status_t SurfaceFlinger::getSupportedFrameTimestamps(
         std::vector<FrameEvent>* outSupported) const {
     *outSupported = {
@@ -1010,9 +1055,7 @@
         FrameEvent::RELEASE,
     };
 
-    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
-
-    if (!getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
+    if (mHasReliablePresentFences) {
         outSupported->push_back(FrameEvent::DISPLAY_PRESENT);
     }
     return NO_ERROR;
@@ -1083,7 +1126,7 @@
 
     for (const auto& [id, mode] : displayModes) {
         ui::DisplayMode outMode;
-        outMode.id = static_cast<int32_t>(id.value());
+        outMode.id = ftl::to_underlying(id);
 
         auto [width, height] = mode->getResolution();
         auto [xDpi, yDpi] = mode->getDpi();
@@ -1103,7 +1146,7 @@
         outMode.peakRefreshRate = peakFps.getValue();
         outMode.vsyncRate = mode->getVsyncRate().getValue();
 
-        const auto vsyncConfigSet = mVsyncConfiguration->getConfigsForRefreshRate(
+        const auto vsyncConfigSet = mScheduler->getVsyncConfiguration().getConfigsForRefreshRate(
                 Fps::fromValue(outMode.peakRefreshRate));
         outMode.appVsyncOffset = vsyncConfigSet.late.appOffset;
         outMode.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
@@ -1132,7 +1175,7 @@
     const PhysicalDisplayId displayId = snapshot.displayId();
 
     const auto mode = display->refreshRateSelector().getActiveMode();
-    info->activeDisplayModeId = mode.modePtr->getId().value();
+    info->activeDisplayModeId = ftl::to_underlying(mode.modePtr->getId());
     info->renderFrameRate = mode.fps.getValue();
     info->activeColorMode = display->getCompositionDisplay()->getState().colorMode;
     info->hdrCapabilities = filterOut4k30(display->getHdrCapabilities());
@@ -1148,7 +1191,7 @@
     if (getHwComposer().hasCapability(Capability::BOOT_DISPLAY_CONFIG)) {
         if (const auto hwcId = getHwComposer().getPreferredBootDisplayMode(displayId)) {
             if (const auto modeId = snapshot.translateModeId(*hwcId)) {
-                info->preferredBootDisplayMode = modeId->value();
+                info->preferredBootDisplayMode = ftl::to_underlying(*modeId);
             }
         }
     }
@@ -1230,8 +1273,10 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool force) {
-    const auto displayId = request.mode.modePtr->getPhysicalDisplayId();
+void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) {
+    const auto mode = desiredMode.mode;
+    const auto displayId = mode.modePtr->getPhysicalDisplayId();
+
     ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const auto display = getDisplayDeviceLocked(displayId);
@@ -1240,14 +1285,13 @@
         return;
     }
 
-    const auto mode = request.mode;
-    const bool emitEvent = request.emitEvent;
+    const bool emitEvent = desiredMode.emitEvent;
 
-    switch (display->setDesiredMode(std::move(request), force)) {
+    switch (display->setDesiredMode(std::move(desiredMode))) {
         case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch:
             // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler.
-            mScheduler->setRenderRate(displayId,
-                                      display->refreshRateSelector().getActiveMode().fps);
+            mScheduler->setRenderRate(displayId, display->refreshRateSelector().getActiveMode().fps,
+                                      /*applyImmediately*/ true);
 
             // Schedule a new frame to initiate the display mode switch.
             scheduleComposite(FrameHint::kNone);
@@ -1262,17 +1306,16 @@
             mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated);
 
             if (displayId == mActiveDisplayId) {
-                updatePhaseConfiguration(mode.fps);
+                mScheduler->updatePhaseConfiguration(mode.fps);
             }
 
             mScheduler->setModeChangePending(true);
             break;
         case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch:
-            mScheduler->setRenderRate(displayId, mode.fps);
+            mScheduler->setRenderRate(displayId, mode.fps, /*applyImmediately*/ false);
 
             if (displayId == mActiveDisplayId) {
-                updatePhaseConfiguration(mode.fps);
-                mRefreshRateStats->setRefreshRate(mode.fps);
+                mScheduler->updatePhaseConfiguration(mode.fps);
             }
 
             if (emitEvent) {
@@ -1293,7 +1336,7 @@
     }
 
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
         const auto displayOpt =
                 FTL_FAKE_GUARD(mStateLock,
                                ftl::find_if(mPhysicalDisplays,
@@ -1312,7 +1355,7 @@
                 [](const DisplayModePtr& mode) { return mode->getPeakFps(); });
 
         if (!fpsOpt) {
-            ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(),
+            ALOGE("%s: Invalid mode %d for display %s", whence, ftl::to_underlying(modeId),
                   to_string(snapshot.displayId()).c_str());
             return BAD_VALUE;
         }
@@ -1364,8 +1407,7 @@
                                activeMode.fps);
 
     if (displayId == mActiveDisplayId) {
-        mRefreshRateStats->setRefreshRate(activeMode.fps);
-        updatePhaseConfiguration(activeMode.fps);
+        mScheduler->updatePhaseConfiguration(activeMode.fps);
     }
 
     if (pendingModeOpt->emitEvent) {
@@ -1391,10 +1433,10 @@
 
     constexpr bool kAllowToEnable = true;
     mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take());
-    mScheduler->setRenderRate(displayId, renderFps);
+    mScheduler->setRenderRate(displayId, renderFps, /*applyImmediately*/ true);
 
     if (displayId == mActiveDisplayId) {
-        updatePhaseConfiguration(renderFps);
+        mScheduler->updatePhaseConfiguration(renderFps);
     }
 }
 
@@ -1412,26 +1454,22 @@
             continue;
         }
 
-        if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
-            dropModeRequest(display);
-            continue;
-        }
-
         const auto desiredModeId = desiredModeOpt->mode.modePtr->getId();
         const auto displayModePtrOpt = physical.snapshot().displayModes().get(desiredModeId);
 
         if (!displayModePtrOpt) {
             ALOGW("Desired display mode is no longer supported. Mode ID = %d",
-                  desiredModeId.value());
-            dropModeRequest(display);
+                  ftl::to_underlying(desiredModeId));
             continue;
         }
 
-        ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(),
+        ALOGV("%s changing active mode to %d(%s) for display %s", __func__,
+              ftl::to_underlying(desiredModeId),
               to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
               to_string(display->getId()).c_str());
 
-        if (display->getActiveMode() == desiredModeOpt->mode) {
+        if ((!FlagManager::getInstance().connected_display() || !desiredModeOpt->force) &&
+            display->getActiveMode() == desiredModeOpt->mode) {
             applyActiveMode(display);
             continue;
         }
@@ -1480,7 +1518,7 @@
 
 void SurfaceFlinger::disableExpensiveRendering() {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         ATRACE_NAME(whence);
         if (mPowerAdvisor->isUsingExpensiveRendering()) {
             for (const auto& [_, display] : mDisplays) {
@@ -1522,7 +1560,7 @@
     }
 
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         const auto displayOpt =
                 ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
                         .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
@@ -1603,7 +1641,7 @@
 status_t SurfaceFlinger::setBootDisplayMode(const sp<display::DisplayToken>& displayToken,
                                             DisplayModeId modeId) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         const auto snapshotOpt =
                 ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
                         .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
@@ -1619,7 +1657,7 @@
                 [](const DisplayModePtr& mode) { return mode->getHwcId(); });
 
         if (!hwcIdOpt) {
-            ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(),
+            ALOGE("%s: Invalid mode %d for display %s", whence, ftl::to_underlying(modeId),
                   to_string(snapshot.displayId()).c_str());
             return BAD_VALUE;
         }
@@ -1631,7 +1669,7 @@
 
 status_t SurfaceFlinger::clearBootDisplayMode(const sp<IBinder>& displayToken) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             return getHwComposer().clearBootDisplayMode(*displayId);
         } else {
@@ -1670,7 +1708,7 @@
         ALOGE("hdrOutputConversion is not supported by this device.");
         return INVALID_OPERATION;
     }
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t {
         using AidlHdrConversionStrategy =
                 aidl::android::hardware::graphics::common::HdrConversionStrategy;
         using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag;
@@ -1730,7 +1768,7 @@
 
 void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             getHwComposer().setAutoLowLatencyMode(*displayId, on);
         } else {
@@ -1741,7 +1779,7 @@
 
 void SurfaceFlinger::setGameContentType(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             const auto type = on ? hal::ContentType::GAME : hal::ContentType::NONE;
             getHwComposer().setContentType(*displayId, type);
@@ -1762,7 +1800,7 @@
     }
 
     display->overrideHdrTypes(hdrTypes);
-    dispatchDisplayHotplugEvent(display->getPhysicalId(), true /* connected */);
+    mScheduler->dispatchHotplug(display->getPhysicalId(), scheduler::Scheduler::Hotplug::Connected);
     return NO_ERROR;
 }
 
@@ -1795,7 +1833,7 @@
                                                           bool enable, uint8_t componentMask,
                                                           uint64_t maxFrames) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             return getHwComposer().setDisplayContentSamplingEnabled(*displayId, enable,
                                                                     componentMask, maxFrames);
@@ -1846,19 +1884,6 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) {
-    outLayers->clear();
-    auto future = mScheduler->schedule([=] {
-        const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
-        mDrawingState.traverseInZOrder([&](Layer* layer) {
-            outLayers->push_back(layer->getLayerDebugInfo(display.get()));
-        });
-    });
-
-    future.wait();
-    return NO_ERROR;
-}
-
 status_t SurfaceFlinger::getCompositionPreference(
         Dataspace* outDataspace, ui::PixelFormat* outPixelFormat,
         Dataspace* outWideColorGamutDataspace,
@@ -1954,7 +1979,8 @@
     }
 
     const char* const whence = __func__;
-    return ftl::Future(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    return ftl::Future(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
+               // TODO(b/241285876): Validate that the display is physical instead of failing later.
                if (const auto display = getDisplayDeviceLocked(displayToken)) {
                    const bool supportsDisplayBrightnessCommand =
                            getHwComposer().getComposer()->isSupported(
@@ -2004,7 +2030,6 @@
                                                      Hwc2::Composer::DisplayBrightnessOptions{
                                                              .applyImmediately = true});
                    }
-
                } else {
                    ALOGE("%s: Invalid display token %p", whence, displayToken.get());
                    return ftl::yield<status_t>(NAME_NOT_FOUND);
@@ -2091,12 +2116,18 @@
 sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(
         gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration,
         const sp<IBinder>& layerHandle) {
-    const auto& handle =
-            vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger
-            ? mSfConnectionHandle
-            : mAppConnectionHandle;
+    const auto cycle = [&] {
+        if (FlagManager::getInstance().deprecate_vsync_sf()) {
+            ALOGW_IF(vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger,
+                "requested unsupported config eVsyncSourceSurfaceFlinger");
+            return scheduler::Cycle::Render;
+        }
 
-    return mScheduler->createDisplayEventConnection(handle, eventRegistration, layerHandle);
+        return vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger
+              ? scheduler::Cycle::LastComposite
+              : scheduler::Cycle::Render;
+    }();
+    return mScheduler->createDisplayEventConnection(cycle, eventRegistration, layerHandle);
 }
 
 void SurfaceFlinger::scheduleCommit(FrameHint hint) {
@@ -2131,13 +2162,24 @@
 
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
-    if (FlagManager::getInstance().connected_display()) {
+    if (FlagManager::getInstance().connected_display() && timestamp < 0 &&
+        vsyncPeriod.has_value()) {
         // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32
-        if (mIsHotplugErrViaNegVsync && timestamp < 0 && vsyncPeriod.has_value() &&
-            vsyncPeriod.value() == ~0) {
-            int hotplugErrorCode = static_cast<int32_t>(-timestamp);
-            ALOGD("SurfaceFlinger got hotplugErrorCode=%d", hotplugErrorCode);
-            mScheduler->onHotplugConnectionError(mAppConnectionHandle, hotplugErrorCode);
+        if (mIsHotplugErrViaNegVsync && vsyncPeriod.value() == ~0) {
+            const auto errorCode = static_cast<int32_t>(-timestamp);
+            ALOGD("%s: Hotplug error %d for display %" PRIu64, __func__, errorCode, hwcDisplayId);
+            mScheduler->dispatchHotplugError(errorCode);
+            return;
+        }
+
+        if (mIsHdcpViaNegVsync && vsyncPeriod.value() == ~1) {
+            const int32_t value = static_cast<int32_t>(-timestamp);
+            // one byte is good enough to encode android.hardware.drm.HdcpLevel
+            const int32_t maxLevel = (value >> 8) & 0xFF;
+            const int32_t connectedLevel = value & 0xFF;
+            ALOGD("%s: HDCP levels changed (connected=%d, max=%d) for display %" PRIu64, __func__,
+                  connectedLevel, maxLevel, hwcDisplayId);
+            updateHdcpLevels(hwcDisplayId, connectedLevel, maxLevel);
             return;
         }
     }
@@ -2174,9 +2216,10 @@
     }
 
     if (FlagManager::getInstance().hotplug2()) {
-        ALOGD("SurfaceFlinger got hotplug event=%d", static_cast<int32_t>(event));
         // TODO(b/311403559): use enum type instead of int
-        mScheduler->onHotplugConnectionError(mAppConnectionHandle, static_cast<int32_t>(event));
+        const auto errorCode = static_cast<int32_t>(event);
+        ALOGD("%s: Hotplug error %d for display %" PRIu64, __func__, errorCode, hwcDisplayId);
+        mScheduler->dispatchHotplugError(errorCode);
     }
 }
 
@@ -2207,19 +2250,20 @@
 
 void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) {
     ATRACE_CALL();
-    if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) {
-        const char* const whence = __func__;
-        static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
-            const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported()
-                                                         ? data.refreshPeriodNanos
-                                                         : data.vsyncPeriodNanos);
-            ATRACE_FORMAT("%s Fps %d", whence, fps.getIntValue());
-            const auto display = getDisplayDeviceLocked(*displayId);
-            FTL_FAKE_GUARD(kMainThreadContext,
-                           display->updateRefreshRateOverlayRate(fps, display->getActiveMode().fps,
-                                                                 /* setByHwc */ true));
-        }));
-    }
+    const char* const whence = __func__;
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
+                                                   kMainThreadContext) {
+        if (const auto displayIdOpt = getHwComposer().toPhysicalDisplayId(data.display)) {
+            if (const auto display = getDisplayDeviceLocked(*displayIdOpt)) {
+                const Fps refreshRate = Fps::fromPeriodNsecs(
+                        getHwComposer().getComposer()->isVrrSupported() ? data.refreshPeriodNanos
+                                                                        : data.vsyncPeriodNanos);
+                ATRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue());
+                display->updateRefreshRateOverlayRate(refreshRate, display->getActiveMode().fps,
+                                                      /* showRefreshRate */ true);
+            }
+        }
+    }));
 }
 
 void SurfaceFlinger::configure() {
@@ -2373,11 +2417,7 @@
                 mLegacyLayers[layer->sequence] = layer;
             }
         }
-        if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) {
-            ATRACE_NAME("LayerHierarchyBuilder:update");
-            mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(),
-                                          mLayerLifecycleManager.getDestroyedLayers());
-        }
+        mLayerHierarchyBuilder.update(mLayerLifecycleManager);
     }
 
     // Keep a copy of the drawing state (that is going to be overwritten
@@ -2394,6 +2434,8 @@
         frontend::LayerSnapshotBuilder::Args
                 args{.root = mLayerHierarchyBuilder.getHierarchy(),
                      .layerLifecycleManager = mLayerLifecycleManager,
+                     .includeMetadata = mCompositionEngine->getFeatureFlags().test(
+                             compositionengine::Feature::kSnapshotLayerMetadata),
                      .displays = mFrontEndDisplayInfos,
                      .displayChanges = mFrontEndDisplayInfosChanged,
                      .globalShadowSettings = mDrawingState.globalShadowSettings,
@@ -2421,82 +2463,92 @@
         mUpdateAttachedChoreographer = true;
     }
     outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0;
-    mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0;
+    if (FlagManager::getInstance().vrr_bugfix_24q4()) {
+        mustComposite |= mLayerLifecycleManager.getGlobalChanges().any(
+                frontend::RequestedLayerState::kMustComposite);
+    } else {
+        mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0;
+    }
 
     bool newDataLatched = false;
-    if (!mLegacyFrontEndEnabled) {
-        ATRACE_NAME("DisplayCallbackAndStatsUpdates");
-        mustComposite |= applyTransactionsLocked(update.transactions, vsyncId);
-        traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
-        const nsecs_t latchTime = systemTime();
-        bool unused = false;
+    ATRACE_NAME("DisplayCallbackAndStatsUpdates");
+    mustComposite |= applyTransactionsLocked(update.transactions, vsyncId);
+    traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
+    const nsecs_t latchTime = systemTime();
+    bool unused = false;
 
-        for (auto& layer : mLayerLifecycleManager.getLayers()) {
-            if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) &&
-                layer->bgColorLayer) {
-                sp<Layer> bgColorLayer = getFactory().createEffectLayer(
-                        LayerCreationArgs(this, nullptr, layer->name,
-                                          ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(),
-                                          std::make_optional(layer->id), true));
-                mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
+    for (auto& layer : mLayerLifecycleManager.getLayers()) {
+        if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) &&
+            layer->bgColorLayer) {
+            sp<Layer> bgColorLayer = getFactory().createEffectLayer(
+                    LayerCreationArgs(this, nullptr, layer->name,
+                                      ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(),
+                                      std::make_optional(layer->id), true));
+            mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
+        }
+        const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
+
+        auto it = mLegacyLayers.find(layer->id);
+        if (it == mLegacyLayers.end() &&
+            layer->changes.test(frontend::RequestedLayerState::Changes::Destroyed)) {
+            // Layer handle was created and immediately destroyed. It was destroyed before it
+            // was added to the map.
+            continue;
+        }
+
+        LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                        "Couldnt find layer object for %s",
+                                        layer->getDebugString().c_str());
+        if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
+            if (!it->second->hasBuffer()) {
+                // The last latch time is used to classify a missed frame as buffer stuffing
+                // instead of a missed frame. This is used to identify scenarios where we
+                // could not latch a buffer or apply a transaction due to backpressure.
+                // We only update the latch time for buffer less layers here, the latch time
+                // is updated for buffer layers when the buffer is latched.
+                it->second->updateLastLatchTime(latchTime);
             }
-            const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
-
-            auto it = mLegacyLayers.find(layer->id);
-            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
-                                            "Couldnt find layer object for %s",
-                                            layer->getDebugString().c_str());
-            if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
-                if (!it->second->hasBuffer()) {
-                    // The last latch time is used to classify a missed frame as buffer stuffing
-                    // instead of a missed frame. This is used to identify scenarios where we
-                    // could not latch a buffer or apply a transaction due to backpressure.
-                    // We only update the latch time for buffer less layers here, the latch time
-                    // is updated for buffer layers when the buffer is latched.
-                    it->second->updateLastLatchTime(latchTime);
-                }
-                continue;
-            }
-
-            const bool bgColorOnly =
-                    !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
-            if (willReleaseBufferOnLatch) {
-                mLayersWithBuffersRemoved.emplace(it->second);
-            }
-            it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
-            newDataLatched = true;
-
-            mLayersWithQueuedFrames.emplace(it->second);
-            mLayersIdsWithQueuedFrames.emplace(it->second->sequence);
+            continue;
         }
 
-        updateLayerHistory(latchTime);
-        mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-            if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) ==
-                mLayersIdsWithQueuedFrames.end())
-                return;
-            Region visibleReg;
-            visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
-            invalidateLayerStack(snapshot.outputFilter, visibleReg);
-        });
-
-        for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
-            mLegacyLayers.erase(destroyedLayer->id);
+        const bool bgColorOnly =
+                !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
+        if (willReleaseBufferOnLatch) {
+            mLayersWithBuffersRemoved.emplace(it->second);
         }
+        it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
+        newDataLatched = true;
 
-        {
-            ATRACE_NAME("LLM:commitChanges");
-            mLayerLifecycleManager.commitChanges();
-        }
-
-        // enter boot animation on first buffer latch
-        if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
-            ALOGI("Enter boot animation");
-            mBootStage = BootStage::BOOTANIMATION;
-        }
+        mLayersWithQueuedFrames.emplace(it->second);
+        mLayersIdsWithQueuedFrames.emplace(it->second->sequence);
     }
+
+    updateLayerHistory(latchTime);
+    mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) == mLayersIdsWithQueuedFrames.end())
+            return;
+        Region visibleReg;
+        visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
+        invalidateLayerStack(snapshot.outputFilter, visibleReg);
+    });
+
+    for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
+        mLegacyLayers.erase(destroyedLayer->id);
+    }
+
+    {
+        ATRACE_NAME("LLM:commitChanges");
+        mLayerLifecycleManager.commitChanges();
+    }
+
+    // enter boot animation on first buffer latch
+    if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
+        ALOGI("Enter boot animation");
+        mBootStage = BootStage::BOOTANIMATION;
+    }
+
     mustComposite |= (getTransactionFlags() & ~eTransactionFlushNeeded) || newDataLatched;
-    if (mustComposite && !mLegacyFrontEndEnabled) {
+    if (mustComposite) {
         commitTransactions();
     }
 
@@ -2598,18 +2650,21 @@
                                     mScheduler->getPacesetterRefreshRate());
 
         const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
-        bool transactionsAreEmpty;
-        if (mLegacyFrontEndEnabled) {
-            mustComposite |=
-                    updateLayerSnapshotsLegacy(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
-                                               flushTransactions, transactionsAreEmpty);
-        }
+        bool transactionsAreEmpty = false;
         if (mLayerLifecycleManagerEnabled) {
             mustComposite |=
                     updateLayerSnapshots(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
                                          flushTransactions, transactionsAreEmpty);
         }
 
+        // Tell VsyncTracker that we are going to present this frame before scheduling
+        // setTransactionFlags which will schedule another SF frame. This was if the tracker
+        // needs to adjust the vsync timeline, it will be done before the next frame.
+        if (FlagManager::getInstance().vrr_config() && mustComposite) {
+            mScheduler->getVsyncSchedule()->getTracker().onFrameBegin(
+                pacesetterFrameTarget.expectedPresentTime(),
+                pacesetterFrameTarget.lastSignaledFrameTime());
+        }
         if (transactionFlushNeeded()) {
             setTransactionFlags(eTransactionFlushNeeded);
         }
@@ -2675,6 +2730,8 @@
         if (const auto display = getCompositionDisplayLocked(id)) {
             refreshArgs.outputs.push_back(display);
         }
+
+        refreshArgs.frameTargets.try_emplace(id, &targeter->target());
     }
 
     std::vector<DisplayId> displayIds;
@@ -2701,34 +2758,22 @@
         mLayerMetadataSnapshotNeeded = false;
     }
 
-    if (DOES_CONTAIN_BORDER) {
-        refreshArgs.borderInfoList.clear();
-        mDrawingState.traverse([&refreshArgs](Layer* layer) {
-            if (layer->isBorderEnabled()) {
-                compositionengine::BorderRenderInfo info;
-                info.width = layer->getBorderWidth();
-                info.color = layer->getBorderColor();
-                layer->traverse(LayerVector::StateSet::Drawing, [&info](Layer* ilayer) {
-                    info.layerIds.push_back(ilayer->getSequence());
-                });
-                refreshArgs.borderInfoList.emplace_back(std::move(info));
-            }
-        });
-    }
-
     refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache);
 
-    refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
-    for (auto layer : mLayersWithQueuedFrames) {
-        if (auto layerFE = layer->getCompositionEngineLayerFE())
-            refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+    if (!FlagManager::getInstance().ce_fence_promise()) {
+        refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
+        for (auto& layer : mLayersWithQueuedFrames) {
+            if (const auto& layerFE = layer->getCompositionEngineLayerFE())
+                refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+        }
     }
 
     refreshArgs.outputColorSetting = mDisplayColorSetting;
     refreshArgs.forceOutputColorMode = mForceColorMode;
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
-    refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
+    refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) ||
+            mVisibleRegionsDirty || mDrawingState.colorMatrixChanged;
     refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags();
 
     if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) {
@@ -2743,36 +2788,15 @@
         refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay);
     }
 
-    const Period minFramePeriod = mScheduler->getVsyncSchedule()->minFramePeriod();
-
-    if (!getHwComposer().getComposer()->isSupported(
-                Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
-        pacesetterTarget.wouldPresentEarly(minFramePeriod)) {
-        const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
-
-        // TODO(b/255601557): Calculate and pass per-display values for each FrameTarget.
-        refreshArgs.earliestPresentTime =
-                pacesetterTarget.previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration;
-    }
-
-    const TimePoint expectedPresentTime = pacesetterTarget.expectedPresentTime();
     // TODO(b/255601557) Update frameInterval per display
-    refreshArgs.frameInterval = mScheduler->getNextFrameInterval(pacesetterId, expectedPresentTime);
-    refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
-    refreshArgs.expectedPresentTime = expectedPresentTime.ns();
+    refreshArgs.frameInterval =
+            mScheduler->getNextFrameInterval(pacesetterId, pacesetterTarget.expectedPresentTime());
+    const auto scheduledFrameResultOpt = mScheduler->getScheduledFrameResult();
+    const auto scheduledFrameTimeOpt = scheduledFrameResultOpt
+            ? std::optional{scheduledFrameResultOpt->callbackTime}
+            : std::nullopt;
+    refreshArgs.scheduledFrameTime = scheduledFrameTimeOpt;
     refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
-    {
-        auto& notifyExpectedPresentData = mNotifyExpectedPresentMap[pacesetterId];
-        auto lastExpectedPresentTimestamp = TimePoint::fromNs(
-                notifyExpectedPresentData.lastExpectedPresentTimestamp.load().ns());
-        if (expectedPresentTime > lastExpectedPresentTimestamp) {
-            // If the values are not same, then hint is sent with newer value.
-            // And because composition always follows the notifyExpectedPresentIfRequired, we can
-            // skip updating the lastExpectedPresentTimestamp in this case.
-            notifyExpectedPresentData.lastExpectedPresentTimestamp
-                    .compare_exchange_weak(lastExpectedPresentTimestamp, expectedPresentTime);
-        }
-    }
     // Store the present time just before calling to the composition engine so we could notify
     // the scheduler.
     const auto presentTime = systemTime();
@@ -2802,19 +2826,57 @@
         }
     }
 
-    mCompositionEngine->present(refreshArgs);
-    moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+    refreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    for (auto& [layer, layerFE] : layers) {
+        layer->onPreComposition(refreshArgs.refreshStartTime);
+    }
 
-    for (auto [layer, layerFE] : layers) {
-        CompositionResult compositionResult{layerFE->stealCompositionResult()};
-        layer->onPreComposition(compositionResult.refreshStartTime);
-        for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
-            Layer* clonedFrom = layer->getClonedFrom().get();
-            auto owningLayer = clonedFrom ? clonedFrom : layer;
-            owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
+    if (FlagManager::getInstance().ce_fence_promise()) {
+        for (auto& [layer, layerFE] : layers) {
+            attachReleaseFenceFutureToLayer(layer, layerFE,
+                                            layerFE->mSnapshot->outputFilter.layerStack);
         }
-        if (compositionResult.lastClientCompositionFence) {
-            layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+
+        refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
+        for (auto& layer : mLayersWithQueuedFrames) {
+            if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
+                refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+                // Some layers are not displayed and do not yet have a future release fence
+                if (layerFE->getReleaseFencePromiseStatus() ==
+                            LayerFE::ReleaseFencePromiseStatus::UNINITIALIZED ||
+                    layerFE->getReleaseFencePromiseStatus() ==
+                            LayerFE::ReleaseFencePromiseStatus::FULFILLED) {
+                    // layerStack is invalid because layer is not on a display
+                    attachReleaseFenceFutureToLayer(layer.get(), layerFE.get(),
+                                                    ui::INVALID_LAYER_STACK);
+                }
+            }
+        }
+
+        mCompositionEngine->present(refreshArgs);
+        moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+
+        for (auto& [layer, layerFE] : layers) {
+            CompositionResult compositionResult{layerFE->stealCompositionResult()};
+            if (compositionResult.lastClientCompositionFence) {
+                layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+            }
+        }
+
+    } else {
+        mCompositionEngine->present(refreshArgs);
+        moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+
+        for (auto [layer, layerFE] : layers) {
+            CompositionResult compositionResult{layerFE->stealCompositionResult()};
+            for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
+                Layer* clonedFrom = layer->getClonedFrom().get();
+                auto owningLayer = clonedFrom ? clonedFrom : layer;
+                owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
+            }
+            if (compositionResult.lastClientCompositionFence) {
+                layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+            }
         }
     }
 
@@ -2836,6 +2898,7 @@
         scheduleComposite(FrameHint::kNone);
     }
 
+    mNotifyExpectedPresentMap[pacesetterId].hintStatus = NotifyExpectedPresentHintStatus::Start;
     onCompositionPresented(pacesetterId, frameTargeters, presentTime);
 
     const bool hadGpuComposited =
@@ -3048,15 +3111,15 @@
     // but that should be okay since CompositorTiming has snapping logic.
     const TimePoint compositeTime =
             TimePoint::fromNs(mCompositionEngine->getLastFrameRefreshTimestamp());
-    const Duration presentLatency =
-            getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)
-            ? Duration::zero()
-            : mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime);
+    const Duration presentLatency = mHasReliablePresentFences
+            ? mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime)
+            : Duration::zero();
 
     const auto schedule = mScheduler->getVsyncSchedule();
     const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime);
     const Period vsyncPeriod = schedule->period();
-    const nsecs_t vsyncPhase = mVsyncConfiguration->getCurrentConfigs().late.sfOffset;
+    const nsecs_t vsyncPhase =
+            mScheduler->getVsyncConfiguration().getCurrentConfigs().late.sfOffset;
 
     const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase,
                                             presentLatency.ns());
@@ -3079,8 +3142,13 @@
             auto optDisplay = layerStackToDisplay.get(layerStack);
             if (optDisplay && !optDisplay->get()->isVirtual()) {
                 auto fence = getHwComposer().getPresentFence(optDisplay->get()->getPhysicalId());
-                layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
-                                        ui::INVALID_LAYER_STACK);
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    layer->prepareReleaseCallbacks(ftl::yield<FenceResult>(fence),
+                                                   ui::INVALID_LAYER_STACK);
+                } else {
+                    layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
+                                            ui::INVALID_LAYER_STACK);
+                }
             }
         }
         layer->releasePendingBuffer(presentTime.ns());
@@ -3100,7 +3168,7 @@
     {
         Mutex::Autolock lock(mStateLock);
         if (mFpsReporter) {
-            mFpsReporter->dispatchLayerFps();
+            mFpsReporter->dispatchLayerFps(mLayerHierarchyBuilder.getHierarchy());
         }
 
         if (mTunnelModeEnabledReporter) {
@@ -3122,6 +3190,7 @@
         for (auto& [compositionDisplay, listener] : hdrInfoListeners) {
             HdrLayerInfoReporter::HdrLayerInfo info;
             int32_t maxArea = 0;
+
             auto updateInfoFn =
                     [&](const std::shared_ptr<compositionengine::Display>& compositionDisplay,
                         const frontend::LayerSnapshot& snapshot, const sp<LayerFE>& layerFe) {
@@ -3132,7 +3201,7 @@
                                         compositionDisplay->getOutputLayerForLayer(layerFe);
                                 if (outputLayer) {
                                     const float desiredHdrSdrRatio =
-                                            snapshot.desiredHdrSdrRatio <= 1.f
+                                            snapshot.desiredHdrSdrRatio < 1.f
                                             ? std::numeric_limits<float>::infinity()
                                             : snapshot.desiredHdrSdrRatio;
                                     info.mergeDesiredRatio(desiredHdrSdrRatio);
@@ -3153,7 +3222,8 @@
             if (mLayerLifecycleManagerEnabled) {
                 mLayerSnapshotBuilder.forEachVisibleSnapshot(
                         [&, compositionDisplay = compositionDisplay](
-                                std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                                std::unique_ptr<frontend::LayerSnapshot>&
+                                        snapshot) FTL_FAKE_GUARD(kMainThreadContext) {
                             auto it = mLegacyLayers.find(snapshot->sequence);
                             LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
                                                             "Couldnt find layer object for %s",
@@ -3212,7 +3282,7 @@
 
     if (mNumTrustedPresentationListeners > 0) {
         // We avoid any reverse traversal upwards so this shouldn't be too expensive
-        traverseLegacyLayers([&](Layer* layer) {
+        traverseLegacyLayers([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) {
             if (!layer->hasTrustedPresentationListener()) {
                 return;
             }
@@ -3308,7 +3378,11 @@
 std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes(
         PhysicalDisplayId displayId) const {
     std::vector<HWComposer::HWCDisplayMode> hwcModes;
-    std::optional<hal::HWDisplayId> activeModeHwcId;
+    std::optional<hal::HWConfigId> activeModeHwcIdOpt;
+
+    const bool isExternalDisplay = FlagManager::getInstance().connected_display() &&
+            getHwComposer().getDisplayConnectionType(displayId) ==
+                    ui::DisplayConnectionType::External;
 
     int attempt = 0;
     constexpr int kMaxAttempts = 3;
@@ -3316,10 +3390,81 @@
         hwcModes = getHwComposer().getModes(displayId,
                                             scheduler::RefreshRateSelector::kMinSupportedFrameRate
                                                     .getPeriodNsecs());
-        activeModeHwcId = getHwComposer().getActiveMode(displayId);
+        const auto activeModeHwcIdExp = getHwComposer().getActiveMode(displayId);
+        activeModeHwcIdOpt = activeModeHwcIdExp.value_opt();
 
-        const auto isActiveMode = [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) {
-            return mode.hwcId == activeModeHwcId;
+        if (isExternalDisplay &&
+            activeModeHwcIdExp.has_error([](status_t error) { return error == NO_INIT; })) {
+            constexpr nsecs_t k59HzVsyncPeriod = 16949153;
+            constexpr nsecs_t k60HzVsyncPeriod = 16666667;
+
+            // DM sets the initial mode for an external display to 1080p@60, but
+            // this comes after SF creates its own state (including the
+            // DisplayDevice). For now, pick the same mode in order to avoid
+            // inconsistent state and unnecessary mode switching.
+            // TODO (b/318534874): Let DM decide the initial mode.
+            //
+            // Try to find 1920x1080 @ 60 Hz
+            if (const auto iter = std::find_if(hwcModes.begin(), hwcModes.end(),
+                                               [](const auto& mode) {
+                                                   return mode.width == 1920 &&
+                                                           mode.height == 1080 &&
+                                                           mode.vsyncPeriod == k60HzVsyncPeriod;
+                                               });
+                iter != hwcModes.end()) {
+                activeModeHwcIdOpt = iter->hwcId;
+                break;
+            }
+
+            // Try to find 1920x1080 @ 59-60 Hz
+            if (const auto iter = std::find_if(hwcModes.begin(), hwcModes.end(),
+                                               [](const auto& mode) {
+                                                   return mode.width == 1920 &&
+                                                           mode.height == 1080 &&
+                                                           mode.vsyncPeriod >= k60HzVsyncPeriod &&
+                                                           mode.vsyncPeriod <= k59HzVsyncPeriod;
+                                               });
+                iter != hwcModes.end()) {
+                activeModeHwcIdOpt = iter->hwcId;
+                break;
+            }
+
+            // The display does not support 1080p@60, and this is the last attempt to pick a display
+            // mode. Prefer 60 Hz if available, with the closest resolution to 1080p.
+            if (attempt + 1 == kMaxAttempts) {
+                std::vector<HWComposer::HWCDisplayMode> hwcModeOpts;
+
+                for (const auto& mode : hwcModes) {
+                    if (mode.width <= 1920 && mode.height <= 1080 &&
+                        mode.vsyncPeriod >= k60HzVsyncPeriod &&
+                        mode.vsyncPeriod <= k59HzVsyncPeriod) {
+                        hwcModeOpts.push_back(mode);
+                    }
+                }
+
+                if (const auto iter = std::max_element(hwcModeOpts.begin(), hwcModeOpts.end(),
+                                                       [](const auto& a, const auto& b) {
+                                                           const auto aSize = a.width * a.height;
+                                                           const auto bSize = b.width * b.height;
+                                                           if (aSize < bSize)
+                                                               return true;
+                                                           else if (aSize == bSize)
+                                                               return a.vsyncPeriod > b.vsyncPeriod;
+                                                           else
+                                                               return false;
+                                                       });
+                    iter != hwcModeOpts.end()) {
+                    activeModeHwcIdOpt = iter->hwcId;
+                    break;
+                }
+
+                // hwcModeOpts was empty, use hwcModes[0] as the last resort
+                activeModeHwcIdOpt = hwcModes[0].hwcId;
+            }
+        }
+
+        const auto isActiveMode = [activeModeHwcIdOpt](const HWComposer::HWCDisplayMode& mode) {
+            return mode.hwcId == activeModeHwcIdOpt;
         };
 
         if (std::any_of(hwcModes.begin(), hwcModes.end(), isActiveMode)) {
@@ -3329,7 +3474,7 @@
 
     if (attempt == kMaxAttempts) {
         const std::string activeMode =
-                activeModeHwcId ? std::to_string(*activeModeHwcId) : "unknown"s;
+                activeModeHwcIdOpt ? std::to_string(*activeModeHwcIdOpt) : "unknown"s;
         ALOGE("HWC failed to report an active mode that is supported: activeModeHwcId=%s, "
               "hwcModes={%s}",
               activeMode.c_str(), base::Join(hwcModes, ", ").c_str());
@@ -3342,15 +3487,15 @@
                                           })
                                           .value_or(DisplayModes{});
 
-    ui::DisplayModeId nextModeId = 1 +
-            std::accumulate(oldModes.begin(), oldModes.end(), static_cast<ui::DisplayModeId>(-1),
-                            [](ui::DisplayModeId max, const auto& pair) {
-                                return std::max(max, pair.first.value());
-                            });
+    DisplayModeId nextModeId = std::accumulate(oldModes.begin(), oldModes.end(), DisplayModeId(-1),
+                                               [](DisplayModeId max, const auto& pair) {
+                                                   return std::max(max, pair.first);
+                                               });
+    ++nextModeId;
 
     DisplayModes newModes;
     for (const auto& hwcMode : hwcModes) {
-        const DisplayModeId id{nextModeId++};
+        const auto id = nextModeId++;
         newModes.try_emplace(id,
                              DisplayMode::Builder(hwcMode.hwcId)
                                      .setId(id)
@@ -3373,10 +3518,14 @@
     // Keep IDs if modes have not changed.
     const auto& modes = sameModes ? oldModes : newModes;
     const DisplayModePtr activeMode =
-            std::find_if(modes.begin(), modes.end(), [activeModeHwcId](const auto& pair) {
-                return pair.second->getHwcId() == activeModeHwcId;
+            std::find_if(modes.begin(), modes.end(), [activeModeHwcIdOpt](const auto& pair) {
+                return pair.second->getHwcId() == activeModeHwcIdOpt;
             })->second;
 
+    if (isExternalDisplay) {
+        ALOGI("External display %s initial mode: {%s}", to_string(displayId).c_str(),
+              to_string(*activeMode).c_str());
+    }
     return {modes, activeMode};
 }
 
@@ -3390,12 +3539,45 @@
     for (const auto [hwcDisplayId, connection] : events) {
         if (auto info = getHwComposer().onHotplug(hwcDisplayId, connection)) {
             const auto displayId = info->id;
-            const bool connected = connection == hal::Connection::CONNECTED;
+            const ftl::Concat displayString("display ", displayId.value, "(HAL ID ", hwcDisplayId,
+                                            ')');
 
-            if (const char* const log =
-                        processHotplug(displayId, hwcDisplayId, connected, std::move(*info))) {
-                ALOGI("%s display %s (HAL ID %" PRIu64 ")", log, to_string(displayId).c_str(),
-                      hwcDisplayId);
+            if (connection == hal::Connection::CONNECTED) {
+                const auto activeModeIdOpt =
+                        processHotplugConnect(displayId, hwcDisplayId, std::move(*info),
+                                              displayString.c_str());
+                if (!activeModeIdOpt) {
+                    if (FlagManager::getInstance().hotplug2()) {
+                        mScheduler->dispatchHotplugError(
+                                static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
+                    }
+                    getHwComposer().disconnectDisplay(displayId);
+                    continue;
+                }
+
+                const auto [kernelIdleTimerController, idleTimerTimeoutMs] =
+                        getKernelIdleTimerProperties(displayId);
+
+                using Config = scheduler::RefreshRateSelector::Config;
+                const Config config =
+                        {.enableFrameRateOverride = sysprop::enable_frame_rate_override(true)
+                                 ? Config::FrameRateOverride::Enabled
+                                 : Config::FrameRateOverride::Disabled,
+                         .frameRateMultipleThreshold =
+                                 base::GetIntProperty("debug.sf.frame_rate_multiple_threshold"s, 0),
+                         .legacyIdleTimerTimeout = idleTimerTimeoutMs,
+                         .kernelIdleTimerController = kernelIdleTimerController};
+
+                const auto snapshotOpt =
+                        mPhysicalDisplays.get(displayId).transform(&PhysicalDisplay::snapshotRef);
+                LOG_ALWAYS_FATAL_IF(!snapshotOpt);
+
+                mDisplayModeController.registerDisplay(*snapshotOpt, *activeModeIdOpt, config);
+            } else {
+                // Unregister before destroying the DisplaySnapshot below.
+                mDisplayModeController.unregisterDisplay(displayId);
+
+                processHotplugDisconnect(displayId, displayString.c_str());
             }
         }
     }
@@ -3403,33 +3585,20 @@
     return !events.empty();
 }
 
-const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId,
-                                           hal::HWDisplayId hwcDisplayId, bool connected,
-                                           DisplayIdentificationInfo&& info) {
-    const auto displayOpt = mPhysicalDisplays.get(displayId);
-    if (!connected) {
-        LOG_ALWAYS_FATAL_IF(!displayOpt);
-        const auto& display = displayOpt->get();
-
-        if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) {
-            mCurrentState.displays.removeItemsAt(index);
-        }
-
-        mPhysicalDisplays.erase(displayId);
-        return "Disconnecting";
-    }
-
+std::optional<DisplayModeId> SurfaceFlinger::processHotplugConnect(PhysicalDisplayId displayId,
+                                                                   hal::HWDisplayId hwcDisplayId,
+                                                                   DisplayIdentificationInfo&& info,
+                                                                   const char* displayString) {
     auto [displayModes, activeMode] = loadDisplayModes(displayId);
     if (!activeMode) {
-        // TODO(b/241286153): Report hotplug failure to the framework.
-        ALOGE("Failed to hotplug display %s", to_string(displayId).c_str());
-        getHwComposer().disconnectDisplay(displayId);
-        return nullptr;
+        ALOGE("Failed to hotplug %s", displayString);
+        return std::nullopt;
     }
 
+    const DisplayModeId activeModeId = activeMode->getId();
     ui::ColorModes colorModes = getHwComposer().getColorModes(displayId);
 
-    if (displayOpt) {
+    if (const auto displayOpt = mPhysicalDisplays.get(displayId)) {
         const auto& display = displayOpt->get();
         const auto& snapshot = display.snapshot();
 
@@ -3448,13 +3617,15 @@
         auto& state = mCurrentState.displays.editValueFor(it->second.token());
         state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId.
         state.physical->activeMode = std::move(activeMode);
-        return "Reconnecting";
+        ALOGI("Reconnecting %s", displayString);
+        return activeModeId;
     }
 
     const sp<IBinder> token = sp<BBinder>::make();
+    const ui::DisplayConnectionType connectionType =
+            getHwComposer().getDisplayConnectionType(displayId);
 
-    mPhysicalDisplays.try_emplace(displayId, token, displayId,
-                                  getHwComposer().getDisplayConnectionType(displayId),
+    mPhysicalDisplays.try_emplace(displayId, token, displayId, connectionType,
                                   std::move(displayModes), std::move(colorModes),
                                   std::move(info.deviceProductInfo));
 
@@ -3462,17 +3633,28 @@
     state.physical = {.id = displayId,
                       .hwcDisplayId = hwcDisplayId,
                       .activeMode = std::move(activeMode)};
-    state.isSecure = true; // All physical displays are currently considered secure.
+    state.isSecure = connectionType == ui::DisplayConnectionType::Internal;
     state.isProtected = true;
     state.displayName = std::move(info.name);
 
     mCurrentState.displays.add(token, state);
-    return "Connecting";
+    ALOGI("Connecting %s", displayString);
+    return activeModeId;
 }
 
-void SurfaceFlinger::dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected) {
-    mScheduler->onHotplugReceived(mAppConnectionHandle, displayId, connected);
-    mScheduler->onHotplugReceived(mSfConnectionHandle, displayId, connected);
+void SurfaceFlinger::processHotplugDisconnect(PhysicalDisplayId displayId,
+                                              const char* displayString) {
+    ALOGI("Disconnecting %s", displayString);
+
+    const auto displayOpt = mPhysicalDisplays.get(displayId);
+    LOG_ALWAYS_FATAL_IF(!displayOpt);
+    const auto& display = displayOpt->get();
+
+    if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) {
+        mCurrentState.displays.removeItemsAt(index);
+    }
+
+    mPhysicalDisplays.erase(displayId);
 }
 
 void SurfaceFlinger::dispatchDisplayModeChangeEvent(PhysicalDisplayId displayId,
@@ -3482,7 +3664,7 @@
             ? &scheduler::Scheduler::onPrimaryDisplayModeChanged
             : &scheduler::Scheduler::onNonPrimaryDisplayModeChanged;
 
-    ((*mScheduler).*onDisplayModeChanged)(mAppConnectionHandle, mode);
+    ((*mScheduler).*onDisplayModeChanged)(scheduler::Cycle::Render, mode);
 }
 
 sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
@@ -3500,43 +3682,21 @@
     creationArgs.hasWideColorGamut = false;
     creationArgs.supportedPerFrameMetadata = 0;
 
-    if (const auto& physical = state.physical) {
-        creationArgs.activeModeId = physical->activeMode->getId();
-        const auto [kernelIdleTimerController, idleTimerTimeoutMs] =
-                getKernelIdleTimerProperties(compositionDisplay->getId());
+    if (const auto physicalIdOpt = PhysicalDisplayId::tryCast(compositionDisplay->getId())) {
+        const auto physicalId = *physicalIdOpt;
 
-        using Config = scheduler::RefreshRateSelector::Config;
-        const auto enableFrameRateOverride = sysprop::enable_frame_rate_override(true)
-                ? Config::FrameRateOverride::Enabled
-                : Config::FrameRateOverride::Disabled;
-        Config config =
-                {.enableFrameRateOverride = enableFrameRateOverride,
-                 .frameRateMultipleThreshold =
-                         base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0),
-                 .idleTimerTimeout = idleTimerTimeoutMs,
-                 .kernelIdleTimerController = kernelIdleTimerController};
-
+        creationArgs.isPrimary = physicalId == getPrimaryDisplayIdLocked();
         creationArgs.refreshRateSelector =
-                mPhysicalDisplays.get(physical->id)
-                        .transform(&PhysicalDisplay::snapshotRef)
-                        .transform([&](const display::DisplaySnapshot& snapshot) {
-                            return std::make_shared<
-                                    scheduler::RefreshRateSelector>(snapshot.displayModes(),
-                                                                    creationArgs.activeModeId,
-                                                                    config);
-                        })
-                        .value_or(nullptr);
+                FTL_FAKE_GUARD(kMainThreadContext,
+                               mDisplayModeController.selectorPtrFor(physicalId));
 
-        creationArgs.isPrimary = physical->id == getPrimaryDisplayIdLocked();
-
-        mPhysicalDisplays.get(physical->id)
+        mPhysicalDisplays.get(physicalId)
                 .transform(&PhysicalDisplay::snapshotRef)
                 .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) {
                     for (const auto mode : snapshot.colorModes()) {
                         creationArgs.hasWideColorGamut |= ui::isWideColorMode(mode);
                         creationArgs.hwcColorModes
-                                .emplace(mode,
-                                         getHwComposer().getRenderIntents(physical->id, mode));
+                                .emplace(mode, getHwComposer().getRenderIntents(physicalId, mode));
                     }
                 }));
     }
@@ -3561,9 +3721,7 @@
             getPhysicalDisplayOrientation(compositionDisplay->getId(), creationArgs.isPrimary);
     ALOGV("Display Orientation: %s", toCString(creationArgs.physicalOrientation));
 
-    // virtual displays are always considered enabled
-    creationArgs.initialPowerMode =
-            state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt;
+    creationArgs.initialPowerMode = state.isVirtual() ? hal::PowerMode::ON : hal::PowerMode::OFF;
 
     creationArgs.requestedRefreshRate = state.requestedRefreshRate;
 
@@ -3665,16 +3823,11 @@
                                                  displaySurface, producer);
 
     if (mScheduler && !display->isVirtual()) {
-        const auto displayId = display->getPhysicalId();
-        {
-            // TODO(b/241285876): Annotate `processDisplayAdded` instead.
-            ftl::FakeGuard guard(kMainThreadContext);
+        // TODO(b/241285876): Annotate `processDisplayAdded` instead.
+        ftl::FakeGuard guard(kMainThreadContext);
 
-            // For hotplug reconnect, renew the registration since display modes have been reloaded.
-            mScheduler->registerDisplay(displayId, display->holdRefreshRateSelector());
-        }
-
-        dispatchDisplayHotplugEvent(displayId, true);
+        // For hotplug reconnect, renew the registration since display modes have been reloaded.
+        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
     }
 
     if (display->isVirtual()) {
@@ -3682,6 +3835,27 @@
     }
 
     mDisplays.try_emplace(displayToken, std::move(display));
+
+    // For an external display, loadDisplayModes already attempted to select the same mode
+    // as DM, but SF still needs to be updated to match.
+    // TODO (b/318534874): Let DM decide the initial mode.
+    if (const auto& physical = state.physical;
+        mScheduler && physical && FlagManager::getInstance().connected_display()) {
+        const bool isInternalDisplay = mPhysicalDisplays.get(physical->id)
+                                               .transform(&PhysicalDisplay::isInternal)
+                                               .value_or(false);
+
+        if (!isInternalDisplay) {
+            auto activeModePtr = physical->activeMode;
+            const auto fps = activeModePtr->getPeakFps();
+
+            setDesiredMode(
+                    {.mode = scheduler::FrameRateMode{fps,
+                                                      ftl::as_non_null(std::move(activeModePtr))},
+                     .emitEvent = false,
+                     .force = true});
+        }
+    }
 }
 
 void SurfaceFlinger::processDisplayRemoved(const wp<IBinder>& displayToken) {
@@ -3692,7 +3866,6 @@
         if (display->isVirtual()) {
             releaseVirtualDisplay(display->getVirtualId());
         } else {
-            dispatchDisplayHotplugEvent(display->getPhysicalId(), false);
             mScheduler->unregisterDisplay(display->getPhysicalId());
         }
     }
@@ -3741,11 +3914,13 @@
 
         if (currentState.physical) {
             const auto display = getDisplayDeviceLocked(displayToken);
-            setPowerModeInternal(display, hal::PowerMode::ON);
+            if (!mSkipPowerOnForQuiescent) {
+                setPowerModeInternal(display, hal::PowerMode::ON);
+            }
 
             // TODO(b/175678251) Call a listener instead.
             if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
-                resetPhaseConfiguration(display->getActiveMode().fps);
+                mScheduler->resetPhaseConfiguration(display->getActiveMode().fps);
             }
         }
         return;
@@ -3781,15 +3956,6 @@
     }
 }
 
-void SurfaceFlinger::resetPhaseConfiguration(Fps refreshRate) {
-    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
-    mScheduler->vsyncModulator().cancelRefreshRateChange();
-
-    mVsyncConfiguration->reset();
-    updatePhaseConfiguration(refreshRate);
-    mRefreshRateStats->setRefreshRate(refreshRate);
-}
-
 void SurfaceFlinger::processDisplayChangesLocked() {
     // here we take advantage of Vector's copy-on-write semantics to
     // improve performance by skipping the transaction entirely when
@@ -3800,6 +3966,9 @@
         mVisibleRegionsDirty = true;
         mUpdateInputInfo = true;
 
+        // Apply the current color matrix to any added or changed display.
+        mCurrentState.colorMatrixChanged = true;
+
         // find the displays that were removed
         // (ie: in drawing state but not in current state)
         // also handle displays that changed
@@ -3889,19 +4058,7 @@
                 }
             }
 
-            if (!hintDisplay) {
-                // NOTE: TEMPORARY FIX ONLY. Real fix should cause layers to
-                // redraw after transform hint changes. See bug 8508397.
-                // could be null when this layer is using a layerStack
-                // that is not visible on any display. Also can occur at
-                // screen off/on times.
-                // U Update: Don't provide stale hints to the clients. For
-                // special cases where we want the app to draw its
-                // first frame before the display is available, we rely
-                // on WMS and DMS to provide the right information
-                // so the client can calculate the hint.
-                layer->skipReportingTransformHint();
-            } else {
+            if (hintDisplay) {
                 layer->updateTransformHint(hintDisplay->getTransformHint());
             }
         });
@@ -4040,7 +4197,7 @@
                     outWindowInfos.push_back(snapshot.inputInfo);
                 });
     } else {
-        mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
+        mDrawingState.traverseInReverseZOrder([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) {
             if (!layer->needsInputInfo()) return;
             const auto opt =
                     mFrontEndDisplayInfos.get(layer->getLayerStack())
@@ -4100,17 +4257,11 @@
 
         if (!display) continue;
 
-        if (ftl::FakeGuard guard(kMainThreadContext);
-            !shouldApplyRefreshRateSelectorPolicy(*display)) {
-            ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str());
-            continue;
-        }
-
         if (display->refreshRateSelector().isModeAllowed(request.mode)) {
             setDesiredMode(std::move(request));
         } else {
-            ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(),
-                  to_string(displayId).c_str());
+            ALOGV("%s: Mode %d is disallowed for display %s", __func__,
+                  ftl::to_underlying(modePtr->getId()), to_string(displayId).c_str());
         }
     }
 }
@@ -4121,7 +4272,7 @@
         return getDefaultDisplayDeviceLocked()->getPhysicalId();
     }();
 
-    mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
+    mScheduler->onFrameRateOverridesChanged(scheduler::Cycle::Render, displayId);
 }
 
 void SurfaceFlinger::notifyCpuLoadUp() {
@@ -4136,8 +4287,9 @@
     }
 }
 
-void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime,
-                                      ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) {
+void SurfaceFlinger::onExpectedPresentTimePosted(TimePoint expectedPresentTime,
+                                                 ftl::NonNull<DisplayModePtr> modePtr,
+                                                 Fps renderRate) {
     const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod();
     const auto timeoutOpt = [&]() -> std::optional<Period> {
         const auto vrrConfig = modePtr->getVrrConfig();
@@ -4146,7 +4298,7 @@
         const auto notifyExpectedPresentConfig =
                 modePtr->getVrrConfig()->notifyExpectedPresentConfig;
         if (!notifyExpectedPresentConfig) return std::nullopt;
-        return Period::fromNs(notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
+        return Period::fromNs(notifyExpectedPresentConfig->timeoutNs);
     }();
 
     notifyExpectedPresentIfRequired(modePtr->getPhysicalDisplayId(), vsyncPeriod,
@@ -4158,45 +4310,128 @@
                                                      TimePoint expectedPresentTime,
                                                      Fps frameInterval,
                                                      std::optional<Period> timeoutOpt) {
-    {
-        auto& data = mNotifyExpectedPresentMap[displayId];
-        const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp.load();
-        const auto lastFrameInterval = data.lastFrameInterval;
-        data.lastFrameInterval = frameInterval;
-        const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
+    auto& data = mNotifyExpectedPresentMap[displayId];
+    const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp;
+    const auto lastFrameInterval = data.lastFrameInterval;
+    data.lastFrameInterval = frameInterval;
+    data.lastExpectedPresentTimestamp = expectedPresentTime;
+    const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
 
-        const constexpr nsecs_t kOneSecondNs =
-                std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
-        const auto timeout = Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns()
-                                                                               : kOneSecondNs);
-        const bool frameIntervalIsOnCadence =
-                isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
-                                         lastFrameInterval, timeout, threshold);
+    const constexpr nsecs_t kOneSecondNs =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+    const auto timeout =
+            Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns() : kOneSecondNs);
+    const bool frameIntervalIsOnCadence =
+            isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
+                                     lastFrameInterval, timeout, threshold);
 
-        const bool expectedPresentWithinTimeout =
-                isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
-                                               timeoutOpt, threshold);
+    const bool expectedPresentWithinTimeout =
+            isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
+                                           timeoutOpt, threshold);
+    if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+        return;
+    }
 
-        using fps_approx_ops::operator!=;
-        if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
-            data.lastExpectedPresentTimestamp = expectedPresentTime;
+    auto hintStatus = data.hintStatus.load();
+    if (!expectedPresentWithinTimeout) {
+        if ((hintStatus != NotifyExpectedPresentHintStatus::Sent &&
+             hintStatus != NotifyExpectedPresentHintStatus::ScheduleOnTx) ||
+            (timeoutOpt && timeoutOpt->ns() == 0)) {
+            // Send the hint immediately if timeout, as the hint gets
+            // delayed otherwise, as the frame is scheduled close
+            // to the actual present.
+            if (data.hintStatus
+                        .compare_exchange_strong(hintStatus,
+                                                 NotifyExpectedPresentHintStatus::ScheduleOnTx)) {
+                scheduleNotifyExpectedPresentHint(displayId);
+                return;
+            }
         }
+    }
 
-        if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
-            return;
-        }
-        data.lastExpectedPresentTimestamp = expectedPresentTime;
+    if (hintStatus == NotifyExpectedPresentHintStatus::Sent &&
+        data.hintStatus.compare_exchange_strong(hintStatus,
+                                                NotifyExpectedPresentHintStatus::ScheduleOnTx)) {
+        return;
+    }
+    if (hintStatus != NotifyExpectedPresentHintStatus::Start) {
+        return;
+    }
+    data.hintStatus.store(NotifyExpectedPresentHintStatus::ScheduleOnPresent);
+    mScheduler->scheduleFrame();
+}
+
+void SurfaceFlinger::scheduleNotifyExpectedPresentHint(PhysicalDisplayId displayId,
+                                                       VsyncId vsyncId) {
+    auto itr = mNotifyExpectedPresentMap.find(displayId);
+    if (itr == mNotifyExpectedPresentMap.end()) {
+        return;
     }
 
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+    const auto sendHint = [=, this]() {
+        auto& data = mNotifyExpectedPresentMap.at(displayId);
+        TimePoint expectedPresentTime = data.lastExpectedPresentTimestamp;
+        if (ftl::to_underlying(vsyncId) != FrameTimelineInfo::INVALID_VSYNC_ID) {
+            const auto predictionOpt = mFrameTimeline->getTokenManager()->getPredictionsForToken(
+                    ftl::to_underlying(vsyncId));
+            const auto expectedPresentTimeOnPredictor = TimePoint::fromNs(
+                    predictionOpt ? predictionOpt->presentTime : expectedPresentTime.ns());
+            const auto scheduledFrameResultOpt = mScheduler->getScheduledFrameResult();
+            const auto expectedPresentTimeOnScheduler = scheduledFrameResultOpt.has_value()
+                    ? scheduledFrameResultOpt->vsyncTime
+                    : TimePoint::fromNs(0);
+            expectedPresentTime =
+                    std::max(expectedPresentTimeOnPredictor, expectedPresentTimeOnScheduler);
+        }
+
+        if (expectedPresentTime < TimePoint::now()) {
+            expectedPresentTime =
+                    mScheduler->getVsyncSchedule()->vsyncDeadlineAfter(TimePoint::now());
+            if (mScheduler->vsyncModulator().getVsyncConfig().sfWorkDuration >
+                mScheduler->getVsyncSchedule(displayId)->period()) {
+                expectedPresentTime += mScheduler->getVsyncSchedule(displayId)->period();
+            }
+        }
         const auto status = getHwComposer().notifyExpectedPresent(displayId, expectedPresentTime,
-                                                                  frameInterval);
+                                                                  data.lastFrameInterval);
         if (status != NO_ERROR) {
             ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, whence,
                   displayId.value);
         }
-    }));
+    };
+
+    if (itr->second.hintStatus == NotifyExpectedPresentHintStatus::ScheduleOnTx) {
+        return static_cast<void>(mScheduler->schedule([=,
+                                                       this]() FTL_FAKE_GUARD(kMainThreadContext) {
+            auto& data = mNotifyExpectedPresentMap.at(displayId);
+            auto scheduleHintOnTx = NotifyExpectedPresentHintStatus::ScheduleOnTx;
+            if (data.hintStatus.compare_exchange_strong(scheduleHintOnTx,
+                                                        NotifyExpectedPresentHintStatus::Sent)) {
+                sendHint();
+            }
+        }));
+    }
+    auto scheduleHintOnPresent = NotifyExpectedPresentHintStatus::ScheduleOnPresent;
+    if (itr->second.hintStatus.compare_exchange_strong(scheduleHintOnPresent,
+                                                       NotifyExpectedPresentHintStatus::Sent)) {
+        sendHint();
+    }
+}
+
+void SurfaceFlinger::sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) {
+    if (auto itr = mNotifyExpectedPresentMap.find(displayId);
+        itr == mNotifyExpectedPresentMap.end() ||
+        itr->second.hintStatus != NotifyExpectedPresentHintStatus::ScheduleOnPresent) {
+        return;
+    }
+    scheduleNotifyExpectedPresentHint(displayId);
+}
+
+void SurfaceFlinger::onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) {
+    if (FlagManager::getInstance().commit_not_composited()) {
+        mFrameTimeline->onCommitNotComposited();
+    }
 }
 
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
@@ -4206,10 +4441,6 @@
 
     const auto activeMode = display->refreshRateSelector().getActiveMode();
     const Fps activeRefreshRate = activeMode.fps;
-    mRefreshRateStats =
-            std::make_unique<RefreshRateStats>(*mTimeStats, activeRefreshRate, hal::PowerMode::OFF);
-
-    mVsyncConfiguration = getFactory().createVsyncConfiguration(activeRefreshRate);
 
     FeatureFlags features;
 
@@ -4226,7 +4457,7 @@
         features |= Feature::kTracePredictedVsync;
     }
     if (!base::GetBoolProperty("debug.sf.vsync_reactor_ignore_present_fences"s, false) &&
-        !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
+        mHasReliablePresentFences) {
         features |= Feature::kPresentFences;
     }
     if (display->refreshRateSelector().kernelIdleTimerController()) {
@@ -4235,48 +4466,44 @@
     if (mBackpressureGpuComposition) {
         features |= Feature::kBackpressureGpuComposition;
     }
-
-    auto modulatorPtr = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs());
+    if (getHwComposer().getComposer()->isSupported(
+                Hwc2::Composer::OptionalFeature::ExpectedPresentTime)) {
+        features |= Feature::kExpectedPresentTime;
+    }
 
     mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this),
                                              static_cast<ISchedulerCallback&>(*this), features,
-                                             std::move(modulatorPtr),
-                                             static_cast<IVsyncTrackerCallback&>(*this));
+                                             getFactory(), activeRefreshRate, *mTimeStats);
+
+    // The pacesetter must be registered before EventThread creation below.
     mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
     if (FlagManager::getInstance().vrr_config()) {
-        mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps);
+        mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps,
+                                  /*applyImmediately*/ true);
     }
-    mScheduler->startTimers();
 
-    const auto configs = mVsyncConfiguration->getCurrentConfigs();
+    const auto configs = mScheduler->getVsyncConfiguration().getCurrentConfigs();
 
-    mAppConnectionHandle =
-            mScheduler->createEventThread(Scheduler::Cycle::Render,
-                                          mFrameTimeline->getTokenManager(),
-                                          /* workDuration */ configs.late.appWorkDuration,
-                                          /* readyDuration */ configs.late.sfWorkDuration);
-    mSfConnectionHandle =
-            mScheduler->createEventThread(Scheduler::Cycle::LastComposite,
-                                          mFrameTimeline->getTokenManager(),
-                                          /* workDuration */ activeRefreshRate.getPeriod(),
-                                          /* readyDuration */ configs.late.sfWorkDuration);
+    mScheduler->createEventThread(scheduler::Cycle::Render, mFrameTimeline->getTokenManager(),
+                                  /* workDuration */ configs.late.appWorkDuration,
+                                  /* readyDuration */ configs.late.sfWorkDuration);
+    mScheduler->createEventThread(scheduler::Cycle::LastComposite,
+                                  mFrameTimeline->getTokenManager(),
+                                  /* workDuration */ activeRefreshRate.getPeriod(),
+                                  /* readyDuration */ configs.late.sfWorkDuration);
 
-    mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(),
-                          *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration);
+    // Dispatch after EventThread creation, since registerDisplay above skipped dispatch.
+    mScheduler->dispatchHotplug(display->getPhysicalId(), scheduler::Scheduler::Hotplug::Connected);
+
+    mScheduler->initVsync(*mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration);
 
     mRegionSamplingThread =
             sp<RegionSamplingThread>::make(*this,
                                            RegionSamplingThread::EnvironmentTimingTunables());
-    mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
+    mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline);
 
-    mIsHotplugErrViaNegVsync =
-            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
-}
-
-void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) {
-    mVsyncConfiguration->setRefreshRateFps(refreshRate);
-    mScheduler->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs(),
-                                  refreshRate.getPeriod());
+    // Timer callbacks may fire, so do this last.
+    mScheduler->startTimers();
 }
 
 void SurfaceFlinger::doCommitTransactions() {
@@ -4310,7 +4537,6 @@
     }
 
     mDrawingState = mCurrentState;
-    // clear the "changed" flags in current state
     mCurrentState.colorMatrixChanged = false;
 
     if (mVisibleRegionsDirty) {
@@ -4458,7 +4684,7 @@
     if (mNumLayers >= MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               MAX_LAYERS);
-        static_cast<void>(mScheduler->schedule([=] {
+        static_cast<void>(mScheduler->schedule([=, this] {
             ALOGE("Dumping layer keeping > 20 children alive:");
             bool leakingParentLayerFound = false;
             mDrawingState.traverse([&](Layer* layer) {
@@ -4577,7 +4803,14 @@
         return TransactionReadiness::NotReady;
     }
 
-    if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
+    const auto vsyncId = VsyncId{transaction.frameTimelineInfo.vsyncId};
+
+    // Transactions with VsyncId are already throttled by the vsyncId (i.e. Choreographer issued
+    // the vsyncId according to the frame rate override cadence) so we shouldn't throttle again
+    // when applying the transaction. Otherwise we might throttle older transactions
+    // incorrectly as the frame rate of SF changed before it drained the older transactions.
+    if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID &&
+        !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
         ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime,
                       transaction.originUid);
         return TransactionReadiness::NotReady;
@@ -4585,8 +4818,7 @@
 
     // If the client didn't specify desiredPresentTime, use the vsyncId to determine the
     // expected present time of this transaction.
-    if (transaction.isAutoTimestamp &&
-        frameIsEarly(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) {
+    if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) {
         ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
                       transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
         return TransactionReadiness::NotReady;
@@ -4670,6 +4902,8 @@
                 if (listener &&
                     (flushState.queueProcessTime - transaction.postTime) >
                             std::chrono::nanoseconds(4s).count()) {
+                    // Used to add a stalled transaction which uses an internal lock.
+                    ftl::FakeGuard guard(kMainThreadContext);
                     mTransactionHandler
                             .onTransactionQueueStalled(transaction.id,
                                                        {.pid = layer->getOwnerPid(),
@@ -4692,97 +4926,107 @@
         const TransactionHandler::TransactionFlushState& flushState) {
     using TransactionReadiness = TransactionHandler::TransactionReadiness;
     auto ready = TransactionReadiness::Ready;
-    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState&
-                                                                           resolvedState) -> bool {
-        const frontend::RequestedLayerState* layer =
-                mLayerLifecycleManager.getLayerFromId(resolvedState.layerId);
-        const auto& transaction = *flushState.transaction;
-        const auto& s = resolvedState.state;
-        // check for barrier frames
-        if (s.bufferData->hasBarrier) {
-            // The current producerId is already a newer producer than the buffer that has a
-            // barrier. This means the incoming buffer is older and we can release it here. We
-            // don't wait on the barrier since we know that's stale information.
-            if (layer->barrierProducerId > s.bufferData->producerId) {
-                if (s.bufferData->releaseBufferListener) {
-                    uint32_t currentMaxAcquiredBufferCount =
-                            getMaxAcquiredBufferCountForCurrentRefreshRate(layer->ownerUid.val());
-                    ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
-                                          layer->name.c_str(), s.bufferData->frameNumber);
-                    s.bufferData->releaseBufferListener
-                            ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()->getId(),
-                                               s.bufferData->frameNumber},
-                                              s.bufferData->acquireFence
-                                                      ? s.bufferData->acquireFence
-                                                      : Fence::NO_FENCE,
-                                              currentMaxAcquiredBufferCount);
+    flushState.transaction->traverseStatesWithBuffersWhileTrue(
+            [&](const ResolvedComposerState& resolvedState) FTL_FAKE_GUARD(
+                    kMainThreadContext) -> bool {
+                const frontend::RequestedLayerState* layer =
+                        mLayerLifecycleManager.getLayerFromId(resolvedState.layerId);
+                const auto& transaction = *flushState.transaction;
+                const auto& s = resolvedState.state;
+                // check for barrier frames
+                if (s.bufferData->hasBarrier) {
+                    // The current producerId is already a newer producer than the buffer that has a
+                    // barrier. This means the incoming buffer is older and we can release it here.
+                    // We don't wait on the barrier since we know that's stale information.
+                    if (layer->barrierProducerId > s.bufferData->producerId) {
+                        if (s.bufferData->releaseBufferListener) {
+                            uint32_t currentMaxAcquiredBufferCount =
+                                    getMaxAcquiredBufferCountForCurrentRefreshRate(
+                                            layer->ownerUid.val());
+                            ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
+                                                  layer->name.c_str(), s.bufferData->frameNumber);
+                            s.bufferData->releaseBufferListener
+                                    ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()
+                                                               ->getId(),
+                                                       s.bufferData->frameNumber},
+                                                      s.bufferData->acquireFence
+                                                              ? s.bufferData->acquireFence
+                                                              : Fence::NO_FENCE,
+                                                      currentMaxAcquiredBufferCount);
+                        }
+
+                        // Delete the entire state at this point and not just release the buffer
+                        // because everything associated with the Layer in this Transaction is now
+                        // out of date.
+                        ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
+                                      layer->name.c_str(), layer->barrierProducerId,
+                                      s.bufferData->producerId);
+                        return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
+                    }
+
+                    if (layer->barrierFrameNumber < s.bufferData->barrierFrameNumber) {
+                        const bool willApplyBarrierFrame =
+                                flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
+                                ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
+                                  s.bufferData->barrierFrameNumber));
+                        if (!willApplyBarrierFrame) {
+                            ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64
+                                          " > %" PRId64,
+                                          layer->name.c_str(), layer->barrierFrameNumber,
+                                          s.bufferData->barrierFrameNumber);
+                            ready = TransactionReadiness::NotReadyBarrier;
+                            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+                        }
+                    }
                 }
 
-                // Delete the entire state at this point and not just release the buffer because
-                // everything associated with the Layer in this Transaction is now out of date.
-                ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d", layer->name.c_str(),
-                              layer->barrierProducerId, s.bufferData->producerId);
-                return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
-            }
-
-            if (layer->barrierFrameNumber < s.bufferData->barrierFrameNumber) {
-                const bool willApplyBarrierFrame =
-                        flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
-                        ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
-                          s.bufferData->barrierFrameNumber));
-                if (!willApplyBarrierFrame) {
-                    ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64,
-                                  layer->name.c_str(), layer->barrierFrameNumber,
-                                  s.bufferData->barrierFrameNumber);
-                    ready = TransactionReadiness::NotReadyBarrier;
+                // If backpressure is enabled and we already have a buffer to commit, keep
+                // the transaction in the queue.
+                const bool hasPendingBuffer =
+                        flushState.bufferLayersReadyToPresent.contains(s.surface.get());
+                if (layer->backpressureEnabled() && hasPendingBuffer &&
+                    transaction.isAutoTimestamp) {
+                    ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
+                    ready = TransactionReadiness::NotReady;
                     return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                 }
-            }
-        }
 
-        // If backpressure is enabled and we already have a buffer to commit, keep
-        // the transaction in the queue.
-        const bool hasPendingBuffer =
-                flushState.bufferLayersReadyToPresent.contains(s.surface.get());
-        if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
-            ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
-            ready = TransactionReadiness::NotReady;
-            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-        }
-
-        const bool acquireFenceAvailable = s.bufferData &&
-                s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
-                s.bufferData->acquireFence;
-        const bool fenceSignaled = !acquireFenceAvailable ||
-                s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
-        if (!fenceSignaled) {
-            // check fence status
-            const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(),
-                                                                    flushState.firstTransaction) &&
-                    layer->isSimpleBufferUpdate(s);
-            if (allowLatchUnsignaled) {
-                ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", layer->name.c_str());
-                ready = TransactionReadiness::NotReadyUnsignaled;
-            } else {
-                ready = TransactionReadiness::NotReady;
-                auto& listener = s.bufferData->releaseBufferListener;
-                if (listener &&
-                    (flushState.queueProcessTime - transaction.postTime) >
-                            std::chrono::nanoseconds(4s).count()) {
-                    mTransactionHandler
-                            .onTransactionQueueStalled(transaction.id,
-                                                       {.pid = layer->ownerPid.val(),
-                                                        .layerId = layer->id,
-                                                        .layerName = layer->name,
-                                                        .bufferId = s.bufferData->getId(),
-                                                        .frameNumber = s.bufferData->frameNumber});
+                const bool acquireFenceAvailable = s.bufferData &&
+                        s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
+                        s.bufferData->acquireFence;
+                const bool fenceSignaled = !acquireFenceAvailable ||
+                        s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
+                if (!fenceSignaled) {
+                    // check fence status
+                    const bool allowLatchUnsignaled =
+                            shouldLatchUnsignaled(s, transaction.states.size(),
+                                                  flushState.firstTransaction) &&
+                            layer->isSimpleBufferUpdate(s);
+                    if (allowLatchUnsignaled) {
+                        ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
+                                      layer->name.c_str());
+                        ready = TransactionReadiness::NotReadyUnsignaled;
+                    } else {
+                        ready = TransactionReadiness::NotReady;
+                        auto& listener = s.bufferData->releaseBufferListener;
+                        if (listener &&
+                            (flushState.queueProcessTime - transaction.postTime) >
+                                    std::chrono::nanoseconds(4s).count()) {
+                            mTransactionHandler
+                                    .onTransactionQueueStalled(transaction.id,
+                                                               {.pid = layer->ownerPid.val(),
+                                                                .layerId = layer->id,
+                                                                .layerName = layer->name,
+                                                                .bufferId = s.bufferData->getId(),
+                                                                .frameNumber =
+                                                                        s.bufferData->frameNumber});
+                        }
+                        ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
+                        return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+                    }
                 }
-                ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
-                return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-            }
-        }
-        return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
-    });
+                return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
+            });
     return ready;
 }
 
@@ -4904,7 +5148,7 @@
     const int originPid = ipc->getCallingPid();
     const int originUid = ipc->getCallingUid();
     uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid);
-    for (auto composerState : states) {
+    for (auto& composerState : states) {
         composerState.state.sanitize(permissions);
     }
 
@@ -5003,7 +5247,18 @@
     }(state.flags);
 
     const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone;
-    mTransactionHandler.queueTransaction(std::move(state));
+    {
+        // Transactions are added via a lockless queue and does not need to be added from the main
+        // thread.
+        ftl::FakeGuard guard(kMainThreadContext);
+        mTransactionHandler.queueTransaction(std::move(state));
+    }
+
+    for (const auto& [displayId, data] : mNotifyExpectedPresentMap) {
+        if (data.hintStatus.load() == NotifyExpectedPresentHintStatus::ScheduleOnTx) {
+            scheduleNotifyExpectedPresentHint(displayId, VsyncId{frameTimelineInfo.vsyncId});
+        }
+    }
     setTransactionFlags(eTransactionFlushNeeded, schedule, applyToken, frameHint);
     return NO_ERROR;
 }
@@ -5033,27 +5288,22 @@
     nsecs_t now = systemTime();
     uint32_t clientStateFlags = 0;
     for (auto& resolvedState : states) {
-        if (mLegacyFrontEndEnabled) {
-            clientStateFlags |=
-                    setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
-                                         isAutoTimestamp, postTime, transactionId);
-
-        } else /*mLayerLifecycleManagerEnabled*/ {
-            clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState,
-                                                             desiredPresentTime, isAutoTimestamp,
-                                                             postTime, transactionId);
-        }
-        if ((flags & eAnimation) && resolvedState.state.surface) {
-            if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
-                const auto layerProps = scheduler::LayerProps{
-                        .visible = layer->isVisible(),
-                        .bounds = layer->getBounds(),
-                        .transform = layer->getTransform(),
-                        .setFrameRateVote = layer->getFrameRateForLayerTree(),
-                        .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
-                        .isFrontBuffered = layer->isFrontBuffered(),
-                };
-                layer->recordLayerHistoryAnimationTx(layerProps, now);
+        clientStateFlags |=
+                updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, desiredPresentTime,
+                                             isAutoTimestamp, postTime, transactionId);
+        if (!mLayerLifecycleManagerEnabled) {
+            if ((flags & eAnimation) && resolvedState.state.surface) {
+                if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
+                    const auto layerProps = scheduler::LayerProps{
+                            .visible = layer->isVisible(),
+                            .bounds = layer->getBounds(),
+                            .transform = layer->getTransform(),
+                            .setFrameRateVote = layer->getFrameRateForLayerTree(),
+                            .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
+                            .isFrontBuffered = layer->isFrontBuffered(),
+                    };
+                    layer->recordLayerHistoryAnimationTx(layerProps, now);
+                }
             }
         }
     }
@@ -5112,7 +5362,7 @@
     }
 
     mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded;
-    if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) {
+    if (mFrontEndDisplayInfosChanged) {
         processDisplayChangesLocked();
         mFrontEndDisplayInfos.clear();
         for (const auto& [_, display] : mDisplays) {
@@ -5321,11 +5571,6 @@
     if (what & layer_state_t::eBlurRegionsChanged) {
         if (layer->setBlurRegions(s.blurRegions)) flags |= eTraversalNeeded;
     }
-    if (what & layer_state_t::eRenderBorderChanged) {
-        if (layer->enableBorder(s.borderEnabled, s.borderWidth, s.borderColor)) {
-            flags |= eTraversalNeeded;
-        }
-    }
     if (what & layer_state_t::eLayerStackChanged) {
         ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer);
         // We only allow setting layer stacks for top level layers,
@@ -5452,6 +5697,11 @@
             flags |= eTraversalNeeded;
         }
     }
+    if (what & layer_state_t::eDesiredHdrHeadroomChanged) {
+        if (layer->setDesiredHdrHeadroom(s.desiredHdrSdrRatio)) {
+            flags |= eTraversalNeeded;
+        }
+    }
     if (what & layer_state_t::eCachingHintChanged) {
         if (layer->setCachingHint(s.cachingHint)) {
             flags |= eTraversalNeeded;
@@ -5461,7 +5711,7 @@
         if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eTrustedOverlayChanged) {
-        if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
+        if (layer->setTrustedOverlay(s.trustedOverlay == gui::TrustedOverlay::ENABLED)) {
             flags |= eTraversalNeeded;
         }
     }
@@ -5637,6 +5887,11 @@
             flags |= eTraversalNeeded;
         }
     }
+    if (what & layer_state_t::eDesiredHdrHeadroomChanged) {
+        if (layer->setDesiredHdrHeadroom(s.desiredHdrSdrRatio)) {
+            flags |= eTraversalNeeded;
+        }
+    }
     if (what & layer_state_t::eBufferChanged) {
         std::optional<ui::Transform::RotationFlags> transformHint = std::nullopt;
         frontend::LayerSnapshot* snapshot = mLayerSnapshotBuilder.getSnapshot(layer->sequence);
@@ -5704,7 +5959,7 @@
             return result;
         }
 
-        mirrorLayer->setClonedChild(mirrorFrom->createClone(mirrorLayer->getSequence()));
+        mirrorLayer->setClonedChild(mirrorFrom->createClone());
     }
 
     outResult.layerId = mirrorLayer->sequence;
@@ -5750,11 +6005,6 @@
         return result;
     }
 
-    if (mLegacyFrontEndEnabled) {
-        std::scoped_lock<std::mutex> lock(mMirrorDisplayLock);
-        mMirrorDisplays.emplace_back(layerStack, outResult.handle, args.client);
-    }
-
     setTransactionFlags(eTransactionFlushNeeded);
     return NO_ERROR;
 }
@@ -5769,7 +6019,7 @@
         case ISurfaceComposerClient::eFXSurfaceContainer:
         case ISurfaceComposerClient::eFXSurfaceBufferState:
             args.flags |= ISurfaceComposerClient::eNoColorFill;
-            FMT_FALLTHROUGH;
+            [[fallthrough]];
         case ISurfaceComposerClient::eFXSurfaceEffect: {
             result = createBufferStateLayer(args, &outResult.handle, &layer);
             std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter();
@@ -5835,7 +6085,11 @@
         mDestroyedHandles.emplace_back(layerId, layer->getDebugName());
     }
 
-    mTransactionHandler.onLayerDestroyed(layerId);
+    {
+        // Used to remove stalled transactions which uses an internal lock.
+        ftl::FakeGuard guard(kMainThreadContext);
+        mTransactionHandler.onLayerDestroyed(layerId);
+    }
 
     Mutex::Autolock lock(mStateLock);
     markLayerPendingRemovalLocked(layer);
@@ -5847,12 +6101,6 @@
 }
 
 void SurfaceFlinger::initializeDisplays() {
-    const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
-    if (!display) return;
-
-    const sp<IBinder> token = display->getDisplayToken().promote();
-    LOG_ALWAYS_FATAL_IF(token == nullptr);
-
     TransactionState state;
     state.inputWindowCommands = mInputWindowCommands;
     const nsecs_t now = systemTime();
@@ -5863,37 +6111,43 @@
     const uint64_t transactionId = (static_cast<uint64_t>(mPid) << 32) | mUniqueTransactionId++;
     state.id = transactionId;
 
-    // reset screen orientation and use primary layer stack
-    DisplayState d;
-    d.what = DisplayState::eDisplayProjectionChanged |
-             DisplayState::eLayerStackChanged;
-    d.token = token;
-    d.layerStack = ui::DEFAULT_LAYER_STACK;
-    d.orientation = ui::ROTATION_0;
-    d.orientedDisplaySpaceRect.makeInvalid();
-    d.layerStackSpaceRect.makeInvalid();
-    d.width = 0;
-    d.height = 0;
-    state.displays.add(d);
+    auto layerStack = ui::DEFAULT_LAYER_STACK.id;
+    for (const auto& [id, display] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) {
+        state.displays.push(DisplayState(display.token(), ui::LayerStack::fromValue(layerStack++)));
+    }
 
     std::vector<TransactionState> transactions;
     transactions.emplace_back(state);
 
-    if (mLegacyFrontEndEnabled) {
-        applyTransactions(transactions, VsyncId{0});
-    } else {
+    {
         Mutex::Autolock lock(mStateLock);
         applyAndCommitDisplayTransactionStatesLocked(transactions);
     }
 
     {
         ftl::FakeGuard guard(mStateLock);
-        setPowerModeInternal(display, hal::PowerMode::ON);
+
+        // In case of a restart, ensure all displays are off.
+        for (const auto& [id, display] : mPhysicalDisplays) {
+            setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::OFF);
+        }
+
+        // Power on all displays. The primary display is first, so becomes the active display. Also,
+        // the DisplayCapability set of a display is populated on its first powering on. Do this now
+        // before responding to any Binder query from DisplayManager about display capabilities.
+        // Additionally, do not turn on displays if the boot should be quiescent.
+        if (!mSkipPowerOnForQuiescent) {
+            for (const auto& [id, display] : mPhysicalDisplays) {
+                setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON);
+            }
+        }
     }
 }
 
 void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode) {
     if (display->isVirtual()) {
+        // TODO(b/241285876): This code path should not be reachable, so enforce this at compile
+        // time.
         ALOGE("%s: Invalid operation on virtual display", __func__);
         return;
     }
@@ -5901,8 +6155,8 @@
     const auto displayId = display->getPhysicalId();
     ALOGD("Setting power mode %d on display %s", mode, to_string(displayId).c_str());
 
-    const auto currentModeOpt = display->getPowerMode();
-    if (currentModeOpt == mode) {
+    const auto currentMode = display->getPowerMode();
+    if (currentMode == mode) {
         return;
     }
 
@@ -5919,7 +6173,7 @@
     display->setPowerMode(mode);
 
     const auto activeMode = display->refreshRateSelector().getActiveMode().modePtr;
-    if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) {
+    if (currentMode == hal::PowerMode::OFF) {
         // Turn on the display
 
         // Activate the display (which involves a modeset to the active mode) when the inner or
@@ -5964,7 +6218,7 @@
         mVisibleRegionsDirty = true;
         scheduleComposite(FrameHint::kActive);
     } else if (mode == hal::PowerMode::OFF) {
-        const bool currentModeNotDozeSuspend = (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND);
+        const bool currentModeNotDozeSuspend = (currentMode != hal::PowerMode::DOZE_SUSPEND);
         // Turn off the display
         if (displayId == mActiveDisplayId) {
             if (const auto display = getActivatableDisplay()) {
@@ -6005,7 +6259,7 @@
     } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) {
         // Update display while dozing
         getHwComposer().setPowerMode(displayId, mode);
-        if (*currentModeOpt == hal::PowerMode::DOZE_SUSPEND &&
+        if (currentMode == hal::PowerMode::DOZE_SUSPEND &&
             (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present())) {
             if (displayId == mActiveDisplayId) {
                 ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON.");
@@ -6033,7 +6287,7 @@
 
     if (displayId == mActiveDisplayId) {
         mTimeStats->setPowerMode(mode);
-        mRefreshRateStats->setPowerMode(mode);
+        mScheduler->setActiveDisplayPowerModeForRefreshRateStats(mode);
     }
 
     mScheduler->setDisplayPowerMode(displayId, mode);
@@ -6042,8 +6296,9 @@
 }
 
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
                                                kMainThreadContext) {
+        mSkipPowerOnForQuiescent = false;
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
             ALOGE("Attempt to set power mode %d for invalid display token %p", mode,
@@ -6096,9 +6351,9 @@
             {"--frontend"s, mainThreadDumper(&SurfaceFlinger::dumpFrontEnd)},
             {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
             {"--hwclayers"s, mainThreadDumper(&SurfaceFlinger::dumpHwcLayersMinidump)},
-            {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
-            {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
-            {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
+            {"--latency"s, argsMainThreadDumper(&SurfaceFlinger::dumpStats)},
+            {"--latency-clear"s, argsMainThreadDumper(&SurfaceFlinger::clearStats)},
+            {"--list"s, mainThreadDumper(&SurfaceFlinger::listLayers)},
             {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
             {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)},
             {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
@@ -6131,28 +6386,29 @@
     return doDump(fd, DumpArgs(), asProto);
 }
 
-void SurfaceFlinger::listLayersLocked(std::string& result) const {
-    mCurrentState.traverseInZOrder(
-            [&](Layer* layer) { StringAppendF(&result, "%s\n", layer->getDebugName()); });
+void SurfaceFlinger::listLayers(std::string& result) const {
+    for (const auto& layer : mLayerLifecycleManager.getLayers()) {
+        StringAppendF(&result, "%s\n", layer->getDebugString().c_str());
+    }
 }
 
-void SurfaceFlinger::dumpStatsLocked(const DumpArgs& args, std::string& result) const {
+void SurfaceFlinger::dumpStats(const DumpArgs& args, std::string& result) const {
     StringAppendF(&result, "%" PRId64 "\n", getVsyncPeriodFromHWC());
     if (args.size() < 2) return;
 
     const auto name = String8(args[1]);
-    mCurrentState.traverseInZOrder([&](Layer* layer) {
+    traverseLegacyLayers([&](Layer* layer) {
         if (layer->getName() == name.c_str()) {
             layer->dumpFrameStats(result);
         }
     });
 }
 
-void SurfaceFlinger::clearStatsLocked(const DumpArgs& args, std::string&) {
+void SurfaceFlinger::clearStats(const DumpArgs& args, std::string&) {
     const bool clearAll = args.size() < 2;
     const auto name = clearAll ? String8() : String8(args[1]);
 
-    mCurrentState.traverse([&](Layer* layer) {
+    traverseLegacyLayers([&](Layer* layer) {
         if (clearAll || layer->getName() == name.c_str()) {
             layer->clearFrameStats();
         }
@@ -6198,10 +6454,6 @@
     dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor);
     dumper.eol();
 
-    mRefreshRateStats->dump(result);
-    dumper.eol();
-
-    mVsyncConfiguration->dump(result);
     StringAppendF(&result,
                   "         present offset: %9" PRId64 " ns\t        VSYNC period: %9" PRId64
                   " ns\n\n",
@@ -6209,7 +6461,7 @@
 }
 
 void SurfaceFlinger::dumpEvents(std::string& result) const {
-    mScheduler->dump(mAppConnectionHandle, result);
+    mScheduler->dump(scheduler::Cycle::Render, result);
 }
 
 void SurfaceFlinger::dumpVsync(std::string& result) const {
@@ -6308,18 +6560,19 @@
     StringAppendF(&result, "DisplayColorSetting: %s\n",
                   decodeDisplayColorSetting(mDisplayColorSetting).c_str());
 
-    // TODO: print out if wide-color mode is active or not
+    // TODO: print out if wide-color mode is active or not.
 
     for (const auto& [id, display] : mPhysicalDisplays) {
         StringAppendF(&result, "Display %s color modes:\n", to_string(id).c_str());
         for (const auto mode : display.snapshot().colorModes()) {
-            StringAppendF(&result, "    %s (%d)\n", decodeColorMode(mode).c_str(), mode);
+            StringAppendF(&result, "    %s (%d)\n", decodeColorMode(mode).c_str(),
+                          fmt::underlying(mode));
         }
 
         if (const auto display = getDisplayDeviceLocked(id)) {
             ui::ColorMode currentMode = display->getCompositionDisplay()->getState().colorMode;
             StringAppendF(&result, "    Current color mode: %s (%d)\n",
-                          decodeColorMode(currentMode).c_str(), currentMode);
+                          decodeColorMode(currentMode).c_str(), fmt::underlying(currentMode));
         }
     }
     result.append("\n");
@@ -6423,17 +6676,6 @@
         }
     }
 
-    if (mLegacyFrontEndEnabled) {
-        perfetto::protos::LayersProto layersProto;
-        for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
-            if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) {
-                continue;
-            }
-            layer->writeToProto(layersProto, traceFlags);
-        }
-        return layersProto;
-    }
-
     return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos,
                                            mLegacyLayers, traceFlags)
             .generate(mLayerHierarchyBuilder.getHierarchy());
@@ -6491,7 +6733,11 @@
 }
 
 perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
-    return mScheduler->schedule([=] { return dumpDrawingStateProto(traceFlags); }).get();
+    return mScheduler
+            ->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+                return dumpDrawingStateProto(traceFlags);
+            })
+            .get();
 }
 
 void SurfaceFlinger::dumpOffscreenLayers(std::string& result) {
@@ -6540,17 +6786,18 @@
         Layer::miniDumpHeader(result);
 
         const DisplayDevice& ref = *display;
-        mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-            if (!snapshot.hasSomethingToDraw() ||
-                ref.getLayerStack() != snapshot.outputFilter.layerStack) {
-                return;
-            }
-            auto it = mLegacyLayers.find(snapshot.sequence);
-            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
-                                            "Couldnt find layer object for %s",
-                                            snapshot.getDebugString().c_str());
-            it->second->miniDump(result, snapshot, ref);
-        });
+        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+                [&](const frontend::LayerSnapshot& snapshot) FTL_FAKE_GUARD(kMainThreadContext) {
+                    if (!snapshot.hasSomethingToDraw() ||
+                        ref.getLayerStack() != snapshot.outputFilter.layerStack) {
+                        return;
+                    }
+                    auto it = mLegacyLayers.find(snapshot.sequence);
+                    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                                    "Couldnt find layer object for %s",
+                                                    snapshot.getDebugString().c_str());
+                    it->second->miniDump(result, snapshot, ref);
+                });
         result.append("\n");
     }
 }
@@ -6647,24 +6894,23 @@
     StringAppendF(&result, "  transaction-flags         : %08x\n", mTransactionFlags.load());
 
     if (const auto display = getDefaultDisplayDeviceLocked()) {
-        std::string fps, xDpi, yDpi;
-        if (const auto activeModePtr =
-                    display->refreshRateSelector().getActiveMode().modePtr.get()) {
-            fps = to_string(activeModePtr->getVsyncRate());
-
+        std::string peakFps, xDpi, yDpi;
+        const auto activeMode = display->refreshRateSelector().getActiveMode();
+        if (const auto activeModePtr = activeMode.modePtr.get()) {
+            peakFps = to_string(activeMode.modePtr->getPeakFps());
             const auto dpi = activeModePtr->getDpi();
             xDpi = base::StringPrintf("%.2f", dpi.x);
             yDpi = base::StringPrintf("%.2f", dpi.y);
         } else {
-            fps = "unknown";
+            peakFps = "unknown";
             xDpi = "unknown";
             yDpi = "unknown";
         }
         StringAppendF(&result,
-                      "  refresh-rate              : %s\n"
+                      "  peak-refresh-rate         : %s\n"
                       "  x-dpi                     : %s\n"
                       "  y-dpi                     : %s\n",
-                      fps.c_str(), xDpi.c_str(), yDpi.c_str());
+                      peakFps.c_str(), xDpi.c_str(), yDpi.c_str());
     }
 
     StringAppendF(&result, "  transaction time: %f us\n", inTransactionDuration / 1000.0);
@@ -6678,10 +6924,6 @@
     }
     result.push_back('\n');
 
-    if (mLegacyFrontEndEnabled) {
-        dumpHwcLayersMinidumpLockedLegacy(result);
-    }
-
     {
         DumpArgs plannerArgs;
         plannerArgs.add(); // first argument is ignored
@@ -6787,8 +7029,8 @@
         // Used by apps to hook Choreographer to SurfaceFlinger.
         case CREATE_DISPLAY_EVENT_CONNECTION:
         case CREATE_CONNECTION:
-        case CREATE_DISPLAY:
-        case DESTROY_DISPLAY:
+        case CREATE_VIRTUAL_DISPLAY:
+        case DESTROY_VIRTUAL_DISPLAY:
         case GET_PRIMARY_PHYSICAL_DISPLAY_ID:
         case GET_PHYSICAL_DISPLAY_IDS:
         case GET_PHYSICAL_DISPLAY_TOKEN:
@@ -6925,6 +7167,7 @@
                 Mutex::Autolock _l(mStateLock);
                 // daltonize
                 n = data.readInt32();
+                mDaltonizer.setLevel(data.readInt32());
                 switch (n % 10) {
                     case 1:
                         mDaltonizer.setType(ColorBlindnessType::Protanomaly);
@@ -6981,14 +7224,15 @@
                 mForceFullDamage = n != 0;
                 return NO_ERROR;
             }
-            case 1018: { // Modify Choreographer's duration
+            case 1018: { // Set the render deadline as a duration until VSYNC.
                 n = data.readInt32();
-                mScheduler->setDuration(mAppConnectionHandle, std::chrono::nanoseconds(n), 0ns);
+                mScheduler->setDuration(scheduler::Cycle::Render, std::chrono::nanoseconds(n), 0ns);
                 return NO_ERROR;
             }
-            case 1019: { // Modify SurfaceFlinger's duration
+            case 1019: { // Set the deadline of the last composite as a duration until VSYNC.
                 n = data.readInt32();
-                mScheduler->setDuration(mSfConnectionHandle, std::chrono::nanoseconds(n), 0ns);
+                mScheduler->setDuration(scheduler::Cycle::LastComposite,
+                                        std::chrono::nanoseconds(n), 0ns);
                 return NO_ERROR;
             }
             case 1020: { // Unused
@@ -7220,7 +7464,7 @@
                 auto inUid = static_cast<uid_t>(data.readInt32());
                 const auto refreshRate = data.readFloat();
                 mScheduler->setPreferredRefreshRateForUid(FrameRateOverride{inUid, refreshRate});
-                mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
+                mScheduler->onFrameRateOverridesChanged(scheduler::Cycle::Render, displayId);
                 return NO_ERROR;
             }
             // Toggle caching feature
@@ -7287,14 +7531,11 @@
                 auto future = mScheduler->schedule(
                         [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
                             n = data.readInt32();
-                            mHdrSdrRatioOverlay = n != 0;
-                            switch (n) {
-                                case 0:
-                                case 1:
-                                    enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay);
-                                    break;
-                                default:
-                                    reply->writeBool(isHdrSdrRatioOverlayEnabled());
+                            if (n == 0 || n == 1) {
+                                mHdrSdrRatioOverlay = n != 0;
+                                enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay);
+                            } else {
+                                reply->writeBool(isHdrSdrRatioOverlayEnabled());
                             }
                         });
                 future.wait();
@@ -7412,7 +7653,7 @@
 
     // Update the overlay on the main thread to avoid race conditions with
     // RefreshRateSelector::getActiveMode
-    static_cast<void>(mScheduler->schedule([=] {
+    static_cast<void>(mScheduler->schedule([=, this] {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         if (!display) {
             ALOGW("%s: default display is null", __func__);
@@ -7434,13 +7675,12 @@
 }
 
 std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
-SurfaceFlinger::getKernelIdleTimerProperties(DisplayId displayId) {
+SurfaceFlinger::getKernelIdleTimerProperties(PhysicalDisplayId displayId) {
     const bool isKernelIdleTimerHwcSupported = getHwComposer().getComposer()->isSupported(
             android::Hwc2::Composer::OptionalFeature::KernelIdleTimer);
     const auto timeout = getIdleTimerTimeout(displayId);
     if (isKernelIdleTimerHwcSupported) {
-        if (const auto id = PhysicalDisplayId::tryCast(displayId);
-            getHwComposer().hasDisplayIdleTimerCapability(*id)) {
+        if (getHwComposer().hasDisplayIdleTimerCapability(displayId)) {
             // In order to decide if we can use the HWC api for idle timer
             // we query DisplayCapability::DISPLAY_IDLE_TIMER directly on the composer
             // without relying on hasDisplayCapability.
@@ -7578,7 +7818,7 @@
 
 status_t SurfaceFlinger::setSchedAttr(bool enabled) {
     static const unsigned int kUclampMin =
-            base::GetUintProperty<unsigned int>("ro.surface_flinger.uclamp.min", 0U);
+            base::GetUintProperty<unsigned int>("ro.surface_flinger.uclamp.min"s, 0U);
 
     if (!kUclampMin) {
         // uclamp.min set to 0 (default), skip setting
@@ -7639,17 +7879,19 @@
 
     status_t validate = validateScreenshotPermissions(args);
     if (validate != OK) {
+        ALOGD("Permission denied to captureDisplay");
         invokeScreenCaptureError(validate, captureListener);
         return;
     }
 
     if (!args.displayToken) {
+        ALOGD("Invalid display token to captureDisplay");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
     if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
-        ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+        ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
         return;
     }
@@ -7662,6 +7904,7 @@
         Mutex::Autolock lock(mStateLock);
         sp<DisplayDevice> display = getDisplayDeviceLocked(args.displayToken);
         if (!display) {
+            ALOGD("Unable to find display device for captureDisplay");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
@@ -7678,18 +7921,13 @@
             if (excludeLayer != UNASSIGNED_LAYER_ID) {
                 excludeLayerIds.emplace(excludeLayer);
             } else {
-                ALOGW("Invalid layer handle passed as excludeLayer to captureDisplay");
+                ALOGD("Invalid layer handle passed as excludeLayer to captureDisplay");
                 invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
                 return;
             }
         }
     }
 
-    RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, args.dataspace,
-                                         args.hintForSeamlessTransition, args.captureSecureLayers);
-    });
-
     GetLayerSnapshotsFunction getLayerSnapshots;
     if (mLayerLifecycleManagerEnabled) {
         getLayerSnapshots =
@@ -7702,8 +7940,12 @@
         getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
     }
 
-    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat,
-                        args.allowProtected, args.grayscale, captureListener);
+    captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<DisplayRenderAreaBuilder>,
+                                                 args.sourceCrop, reqSize, args.dataspace,
+                                                 args.hintForSeamlessTransition,
+                                                 args.captureSecureLayers, displayWeak),
+                        getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected,
+                        args.grayscale, captureListener);
 }
 
 void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args,
@@ -7716,6 +7958,7 @@
 
         const auto display = getDisplayDeviceLocked(displayId);
         if (!display) {
+            ALOGD("Unable to find display device for captureDisplay");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
@@ -7733,17 +7976,11 @@
     constexpr auto kMaxTextureSize = 16384;
     if (size.width <= 0 || size.height <= 0 || size.width >= kMaxTextureSize ||
         size.height >= kMaxTextureSize) {
-        ALOGE("capture display resolved to invalid size %d x %d", size.width, size.height);
+        ALOGD("captureDisplay resolved to invalid size %d x %d", size.width, size.height);
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
-    RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, Rect(), size, args.dataspace,
-                                         args.hintForSeamlessTransition,
-                                         false /* captureSecureLayers */);
-    });
-
     GetLayerSnapshotsFunction getLayerSnapshots;
     if (mLayerLifecycleManagerEnabled) {
         getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID,
@@ -7764,8 +8001,18 @@
     constexpr bool kAllowProtected = false;
     constexpr bool kGrayscale = false;
 
-    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size, args.pixelFormat,
-                        kAllowProtected, kGrayscale, captureListener);
+    captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<DisplayRenderAreaBuilder>,
+                                                 Rect(), size, args.dataspace,
+                                                 args.hintForSeamlessTransition,
+                                                 false /* captureSecureLayers */, displayWeak),
+                        getLayerSnapshots, size, args.pixelFormat, kAllowProtected, kGrayscale,
+                        captureListener);
+}
+
+ScreenCaptureResults SurfaceFlinger::captureLayersSync(const LayerCaptureArgs& args) {
+    sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make();
+    captureLayers(args, captureListener);
+    return captureListener->waitForResults();
 }
 
 void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
@@ -7774,6 +8021,7 @@
 
     status_t validate = validateScreenshotPermissions(args);
     if (validate != OK) {
+        ALOGD("Permission denied to captureLayers");
         invokeScreenCaptureError(validate, captureListener);
         return;
     }
@@ -7785,7 +8033,7 @@
     ui::Dataspace dataspace = args.dataspace;
 
     if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
-        ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+        ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
         return;
     }
@@ -7795,7 +8043,7 @@
 
         parent = LayerHandle::getLayer(args.layerHandle);
         if (parent == nullptr) {
-            ALOGE("captureLayers called with an invalid or removed parent");
+            ALOGD("captureLayers called with an invalid or removed parent");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
@@ -7814,6 +8062,7 @@
         if (crop.isEmpty() || args.frameScaleX <= 0.0f || args.frameScaleY <= 0.0f) {
             // Error out if the layer has no source bounds (i.e. they are boundless) and a source
             // crop was not specified, or an invalid frame scale was provided.
+            ALOGD("Boundless layer, unspecified crop, or invalid frame scale to captureLayers");
             invokeScreenCaptureError(BAD_VALUE, captureListener);
             return;
         }
@@ -7824,7 +8073,7 @@
             if (excludeLayer != UNASSIGNED_LAYER_ID) {
                 excludeLayerIds.emplace(excludeLayer);
             } else {
-                ALOGW("Invalid layer handle passed as excludeLayer to captureLayers");
+                ALOGD("Invalid layer handle passed as excludeLayer to captureLayers");
                 invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
                 return;
             }
@@ -7833,34 +8082,11 @@
 
     // really small crop or frameScale
     if (reqSize.width <= 0 || reqSize.height <= 0) {
-        ALOGW("Failed to captureLayes: crop or scale too small");
+        ALOGD("Failed to captureLayers: crop or scale too small");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
-    bool childrenOnly = args.childrenOnly;
-    RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
-        ui::Transform layerTransform;
-        Rect layerBufferSize;
-        if (mLayerLifecycleManagerEnabled) {
-            frontend::LayerSnapshot* snapshot =
-                    mLayerSnapshotBuilder.getSnapshot(parent->getSequence());
-            if (!snapshot) {
-                ALOGW("Couldn't find layer snapshot for %d", parent->getSequence());
-            } else {
-                layerTransform = snapshot->localTransform;
-                layerBufferSize = snapshot->bufferSize;
-            }
-        } else {
-            layerTransform = parent->getTransform();
-            layerBufferSize = parent->getBufferSize(parent->getDrawingState());
-        }
-
-        return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
-                                                 childrenOnly, args.captureSecureLayers,
-                                                 layerTransform, layerBufferSize,
-                                                 args.hintForSeamlessTransition);
-    });
     GetLayerSnapshotsFunction getLayerSnapshots;
     if (mLayerLifecycleManagerEnabled) {
         std::optional<FloatRect> parentCrop = std::nullopt;
@@ -7898,16 +8124,44 @@
     }
 
     if (captureListener == nullptr) {
-        ALOGE("capture screen must provide a capture listener callback");
+        ALOGD("capture screen must provide a capture listener callback");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
-    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat,
-                        args.allowProtected, args.grayscale, captureListener);
+    captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<LayerRenderAreaBuilder>, crop,
+                                                 reqSize, dataspace, args.captureSecureLayers,
+                                                 args.hintForSeamlessTransition, parent,
+                                                 args.childrenOnly),
+                        getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected,
+                        args.grayscale, captureListener);
 }
 
-void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture,
+// Creates a Future release fence for a layer and keeps track of it in a list to
+// release the buffer when the Future is complete. Calls from composittion
+// involve needing to refresh the composition start time for stats.
+void SurfaceFlinger::attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE,
+                                                     ui::LayerStack layerStack) {
+    ftl::Future<FenceResult> futureFence = layerFE->createReleaseFenceFuture();
+    Layer* clonedFrom = layer->getClonedFrom().get();
+    auto owningLayer = clonedFrom ? clonedFrom : layer;
+    owningLayer->prepareReleaseCallbacks(std::move(futureFence), layerStack);
+}
+
+bool SurfaceFlinger::layersHasProtectedLayer(
+        const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const {
+    bool protectedLayerFound = false;
+    for (auto& [_, layerFe] : layers) {
+        protectedLayerFound |=
+                (layerFe->mSnapshot->isVisible && layerFe->mSnapshot->hasProtectedContent);
+        if (protectedLayerFound) {
+            break;
+        }
+    }
+    return protectedLayerFound;
+}
+
+void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuilder,
                                          GetLayerSnapshotsFunction getLayerSnapshots,
                                          ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
                                          bool allowProtected, bool grayscale,
@@ -7929,18 +8183,8 @@
     const bool supportsProtected = getRenderEngine().supportsProtectedContent();
     bool hasProtectedLayer = false;
     if (allowProtected && supportsProtected) {
-        hasProtectedLayer = mScheduler
-                                    ->schedule([=]() {
-                                        bool protectedLayerFound = false;
-                                        auto layers = getLayerSnapshots();
-                                        for (auto& [_, layerFe] : layers) {
-                                            protectedLayerFound |=
-                                                    (layerFe->mSnapshot->isVisible &&
-                                                     layerFe->mSnapshot->hasProtectedContent);
-                                        }
-                                        return protectedLayerFound;
-                                    })
-                                    .get();
+        auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get();
+        hasProtectedLayer = layersHasProtectedLayer(layers);
     }
     const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
     const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
@@ -7965,52 +8209,78 @@
             renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
                                                  renderengine::impl::ExternalTexture::Usage::
                                                          WRITEABLE);
-    auto fence = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture,
-                                     false /* regionSampling */, grayscale, isProtected,
-                                     captureListener);
-    fence.get();
+    auto futureFence =
+            captureScreenshot(renderAreaBuilder, getLayerSnapshots, texture,
+                              false /* regionSampling */, grayscale, isProtected, captureListener);
+    futureFence.get();
 }
 
-ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
-        RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
+ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
+        RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshots,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
 
-    auto future = mScheduler->schedule(
-            [=, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
-                    kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
-                ScreenCaptureResults captureResults;
-                std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get();
-                if (!renderArea) {
-                    ALOGW("Skipping screen capture because of invalid render area.");
-                    if (captureListener) {
-                        captureResults.fenceResult = base::unexpected(NO_MEMORY);
+    auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
+                                    kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
+        // LayerSnapshots must be obtained from the main thread.
+        auto layers = getLayerSnapshots();
+
+        if (auto* layerRenderAreaBuilder =
+                    std::get_if<LayerRenderAreaBuilder>(&renderAreaBuilder)) {
+            // LayerSnapshotBuilder should only be accessed from the main thread.
+            frontend::LayerSnapshot* snapshot =
+                    mLayerSnapshotBuilder.getSnapshot(layerRenderAreaBuilder->layer->getSequence());
+            if (!snapshot) {
+                ALOGW("Couldn't find layer snapshot for %d",
+                      layerRenderAreaBuilder->layer->getSequence());
+            } else {
+                layerRenderAreaBuilder->setLayerSnapshot(*snapshot);
+            }
+        }
+
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            for (auto& [layer, layerFE] : layers) {
+                attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
+            }
+        }
+
+        ScreenCaptureResults captureResults;
+        std::unique_ptr<const RenderArea> renderArea =
+                std::visit([](auto&& arg) -> std::unique_ptr<RenderArea> { return arg.build(); },
+                           renderAreaBuilder);
+
+        if (!renderArea) {
+            ALOGW("Skipping screen capture because of invalid render area.");
+            if (captureListener) {
+                captureResults.fenceResult = base::unexpected(NO_MEMORY);
+                captureListener->onScreenCaptureCompleted(captureResults);
+            }
+            return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
+        }
+
+        ftl::SharedFuture<FenceResult> renderFuture =
+                renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale,
+                                 isProtected, captureResults, layers);
+
+        if (captureListener) {
+            // Defer blocking on renderFuture back to the Binder thread.
+            return ftl::Future(std::move(renderFuture))
+                    .then([captureListener, captureResults = std::move(captureResults)](
+                                  FenceResult fenceResult) mutable -> FenceResult {
+                        captureResults.fenceResult = std::move(fenceResult);
                         captureListener->onScreenCaptureCompleted(captureResults);
-                    }
-                    return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
-                }
+                        return base::unexpected(NO_ERROR);
+                    })
+                    .share();
+        }
+        return renderFuture;
+    };
 
-                ftl::SharedFuture<FenceResult> renderFuture;
-                renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
-                    renderFuture =
-                            renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling,
-                                             grayscale, isProtected, captureResults);
-                });
-
-                if (captureListener) {
-                    // Defer blocking on renderFuture back to the Binder thread.
-                    return ftl::Future(std::move(renderFuture))
-                            .then([captureListener, captureResults = std::move(captureResults)](
-                                          FenceResult fenceResult) mutable -> FenceResult {
-                                captureResults.fenceResult = std::move(fenceResult);
-                                captureListener->onScreenCaptureCompleted(captureResults);
-                                return base::unexpected(NO_ERROR);
-                            })
-                            .share();
-                }
-                return renderFuture;
-            });
+    // TODO(b/294936197): Run takeScreenshotsFn() in a binder thread to reduce the number
+    // of calls on the main thread.
+    auto future =
+            mScheduler->schedule(FTL_FAKE_GUARD(kMainThreadContext, std::move(takeScreenshotFn)));
 
     // Flatten nested futures.
     auto chain = ftl::Future(std::move(future)).then([](ftl::SharedFuture<FenceResult> future) {
@@ -8021,12 +8291,20 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        std::shared_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
+        std::unique_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) {
-    ATRACE_CALL();
-
     auto layers = getLayerSnapshots();
+    return renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, isProtected,
+                            captureResults, layers);
+}
+
+ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
+        std::unique_ptr<const RenderArea> renderArea,
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+        bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
+        std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers) {
+    ATRACE_CALL();
 
     for (auto& [_, layerFE] : layers) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
@@ -8052,9 +8330,14 @@
         Mutex::Autolock lock(mStateLock);
         const DisplayDevice* display = nullptr;
         if (parent) {
-            display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) {
-                          return display.getLayerStack() == layerStack;
-                      }).get();
+            const frontend::LayerSnapshot* snapshot =
+                    mLayerSnapshotBuilder.getSnapshot(parent->sequence);
+            if (snapshot) {
+                display = findDisplay([layerStack = snapshot->outputFilter.layerStack](
+                                              const auto& display) {
+                              return display.getLayerStack() == layerStack;
+                          }).get();
+            }
         }
 
         if (display == nullptr) {
@@ -8072,10 +8355,7 @@
                                       renderArea->getHintForSeamlessTransition());
             sdrWhitePointNits = state.sdrWhitePointNits;
 
-             // TODO(b/298219334): Clean this up once we verify this doesn't break anything
-             static constexpr bool kScreenshotsDontDim = true;
-
-            if (kScreenshotsDontDim && !captureResults.capturedHdrLayers) {
+            if (!captureResults.capturedHdrLayers) {
                 displayBrightnessNits = sdrWhitePointNits;
             } else {
                 displayBrightnessNits = state.displayBrightnessNits;
@@ -8177,23 +8457,29 @@
     //
     // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call
     // to CompositionEngine::present.
-    const bool renderEngineIsThreaded = [&]() {
-        using Type = renderengine::RenderEngine::RenderEngineType;
-        const auto type = mRenderEngine->getRenderEngineType();
-        return type == Type::SKIA_GL_THREADED;
-    }();
-    auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share()
-                                                : ftl::yield(present()).share();
+    auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
+                                                     : ftl::yield(present()).share();
 
-    for (auto& [layer, layerFE] : layers) {
-        layer->onLayerDisplayed(ftl::Future(presentFuture)
-                                        .then([layerFE = std::move(layerFE)](FenceResult) {
+    if (!FlagManager::getInstance().ce_fence_promise()) {
+        for (auto& [layer, layerFE] : layers) {
+            layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK,
+                                    [layerFE = std::move(layerFE)](FenceResult) {
+                                        if (FlagManager::getInstance()
+                                                    .screenshot_fence_preservation()) {
+                                            const auto compositionResult =
+                                                    layerFE->stealCompositionResult();
+                                            const auto& fences = compositionResult.releaseFences;
+                                            // CompositionEngine may choose to cull layers that
+                                            // aren't visible, so pass a non-fence.
+                                            return fences.empty() ? Fence::NO_FENCE
+                                                                  : fences.back().first.get();
+                                        } else {
                                             return layerFE->stealCompositionResult()
                                                     .releaseFences.back()
                                                     .first.get();
-                                        })
-                                        .share(),
-                                ui::INVALID_LAYER_STACK);
+                                        }
+                                    });
+        }
     }
 
     return presentFuture;
@@ -8301,45 +8587,21 @@
             break;
     }
 
-    if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
-        ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str());
-        return NO_ERROR;
-    }
-
     return applyRefreshRateSelectorPolicy(displayId, selector);
 }
 
-bool SurfaceFlinger::shouldApplyRefreshRateSelectorPolicy(const DisplayDevice& display) const {
-    if (display.isPoweredOn() || mPhysicalDisplays.size() == 1) return true;
-
-    LOG_ALWAYS_FATAL_IF(display.isVirtual());
-    const auto displayId = display.getPhysicalId();
-
-    // The display is powered off, and this is a multi-display device. If the display is the
-    // inactive internal display of a dual-display foldable, then the policy will be applied
-    // when it becomes active upon powering on.
-    //
-    // TODO(b/255635711): Remove this function (i.e. returning `false` as a special case) once
-    // concurrent mode setting across multiple (potentially powered off) displays is supported.
-    //
-    return displayId == mActiveDisplayId ||
-            !mPhysicalDisplays.get(displayId)
-                     .transform(&PhysicalDisplay::isInternal)
-                     .value_or(false);
-}
-
 status_t SurfaceFlinger::applyRefreshRateSelectorPolicy(
-        PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) {
+        PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector) {
     const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy();
     ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str());
 
     // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might
     // be depending in this callback.
     if (const auto activeMode = selector.getActiveMode(); displayId == mActiveDisplayId) {
-        mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode);
+        mScheduler->onPrimaryDisplayModeChanged(scheduler::Cycle::Render, activeMode);
         toggleKernelIdleTimer();
     } else {
-        mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode);
+        mScheduler->onNonPrimaryDisplayModeChanged(scheduler::Cycle::Render, activeMode);
     }
 
     auto preferredModeOpt = getPreferredDisplayMode(displayId, currentPolicy.defaultMode);
@@ -8352,15 +8614,15 @@
     const auto preferredModeId = preferredMode.modePtr->getId();
 
     const Fps preferredFps = preferredMode.fps;
-    ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(),
+    ALOGV("Switching to Scheduler preferred mode %d (%s)", ftl::to_underlying(preferredModeId),
           to_string(preferredFps).c_str());
 
     if (!selector.isModeAllowed(preferredMode)) {
-        ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value());
+        ALOGE("%s: Preferred mode %d is disallowed", __func__, ftl::to_underlying(preferredModeId));
         return INVALID_OPERATION;
     }
 
-    setDesiredMode({std::move(preferredMode), .emitEvent = true}, force);
+    setDesiredMode({std::move(preferredMode), .emitEvent = true});
 
     // Update the frameRateOverride list as the display render rate might have changed
     if (mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps)) {
@@ -8403,7 +8665,7 @@
         return BAD_VALUE;
     }
 
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken));
         if (!display) {
             ALOGE("Attempt to set desired display modes for invalid display token %p",
@@ -8414,8 +8676,13 @@
             return INVALID_OPERATION;
         } else {
             using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy;
+            const auto idleScreenConfigOpt =
+                    FlagManager::getInstance().idle_screen_refresh_rate_timeout()
+                    ? specs.idleScreenRefreshRateConfig
+                    : std::nullopt;
             const Policy policy{DisplayModeId(specs.defaultMode), translate(specs.primaryRanges),
-                                translate(specs.appRequestRanges), specs.allowGroupSwitching};
+                                translate(specs.appRequestRanges), specs.allowGroupSwitching,
+                                idleScreenConfigOpt};
 
             return setDesiredDisplayModeSpecsInternal(display, policy);
         }
@@ -8444,7 +8711,7 @@
 
     scheduler::RefreshRateSelector::Policy policy =
             display->refreshRateSelector().getDisplayManagerPolicy();
-    outSpecs->defaultMode = policy.defaultMode.value();
+    outSpecs->defaultMode = ftl::to_underlying(policy.defaultMode);
     outSpecs->allowGroupSwitching = policy.allowGroupSwitching;
     outSpecs->primaryRanges = translate(policy.primaryRanges);
     outSpecs->appRequestRanges = translate(policy.appRequestRanges);
@@ -8527,7 +8794,7 @@
     }();
 
     mScheduler->setGameModeFrameRateForUid(FrameRateOverride{static_cast<uid_t>(uid), frameRate});
-    mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
+    mScheduler->onFrameRateOverridesChanged(scheduler::Cycle::Render, displayId);
     return NO_ERROR;
 }
 
@@ -8567,7 +8834,8 @@
                     const auto status =
                             getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable);
                     if (status != NO_ERROR) {
-                        ALOGE("Error updating the refresh rate changed callback debug enabled");
+                        ALOGE("Error %s refresh rate changed callback debug",
+                              enable ? "enabling" : "disabling");
                         enableOverlay(/*setByHwc*/ false);
                     }
                 }
@@ -8627,7 +8895,8 @@
 }
 
 int SurfaceFlinger::getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const {
-    const auto vsyncConfig = mVsyncConfiguration->getConfigsForRefreshRate(refreshRate).late;
+    const auto vsyncConfig =
+            mScheduler->getVsyncConfiguration().getConfigsForRefreshRate(refreshRate).late;
     const auto presentLatency = vsyncConfig.appWorkDuration + vsyncConfig.sfWorkDuration;
     return calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
 }
@@ -8682,7 +8951,11 @@
         return;
     }
 
-    mRegionSamplingThread->onCompositionComplete(mScheduler->getScheduledFrameTime());
+    const auto scheduledFrameResultOpt = mScheduler->getScheduledFrameResult();
+    const auto scheduleFrameTimeOpt = scheduledFrameResultOpt
+            ? std::optional{scheduledFrameResultOpt->callbackTime}
+            : std::nullopt;
+    mRegionSamplingThread->onCompositionComplete(scheduleFrameTimeOpt);
 }
 
 void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) {
@@ -8708,19 +8981,14 @@
                                                   const DisplayDevice& activeDisplay) {
     ATRACE_CALL();
 
-    // For the first display activated during boot, there is no need to force setDesiredMode,
-    // because DM is about to send its policy via setDesiredDisplayModeSpecs.
-    bool forceApplyPolicy = false;
-
     if (inactiveDisplayPtr) {
         inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
-        forceApplyPolicy = true;
     }
 
     mActiveDisplayId = activeDisplay.getPhysicalId();
     activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
 
-    resetPhaseConfiguration(activeDisplay.getActiveMode().fps);
+    mScheduler->resetPhaseConfiguration(activeDisplay.getActiveMode().fps);
 
     // TODO(b/255635711): Check for pending mode changes on other displays.
     mScheduler->setModeChangePending(false);
@@ -8731,12 +8999,11 @@
     mActiveDisplayTransformHint = activeDisplay.getTransformHint();
     sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation());
 
-    // The policy of the new active/pacesetter display may have changed while it was inactive. In
-    // that case, its preferred mode has not been propagated to HWC (via setDesiredMode). In either
-    // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode,
-    // and the kernel idle timer of the newly active display must be toggled.
-    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(),
-                                   forceApplyPolicy);
+    // Whether or not the policy of the new active/pacesetter display changed while it was inactive
+    // (in which case its preferred mode has already been propagated to HWC via setDesiredMode), the
+    // Scheduler's cachedModeChangedParams must be initialized to the newly active mode, and the
+    // kernel idle timer of the newly active display must be toggled.
+    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector());
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(const sp<IWindowInfosListener>& windowInfosListener,
@@ -8754,10 +9021,47 @@
 
 status_t SurfaceFlinger::getStalledTransactionInfo(
         int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result) {
+    // Used to add a stalled transaction which uses an internal lock.
+    ftl::FakeGuard guard(kMainThreadContext);
     result = mTransactionHandler.getStalledTransactionInfo(pid);
     return NO_ERROR;
 }
 
+void SurfaceFlinger::updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel,
+                                      int32_t maxLevel) {
+    if (!FlagManager::getInstance().connected_display()) {
+        return;
+    }
+
+    Mutex::Autolock lock(mStateLock);
+
+    const auto idOpt = getHwComposer().toPhysicalDisplayId(hwcDisplayId);
+    if (!idOpt) {
+        ALOGE("No display found for HDCP level changed event: connected=%d, max=%d for "
+              "display=%" PRIu64,
+              connectedLevel, maxLevel, hwcDisplayId);
+        return;
+    }
+
+    const bool isInternalDisplay =
+            mPhysicalDisplays.get(*idOpt).transform(&PhysicalDisplay::isInternal).value_or(false);
+    if (isInternalDisplay) {
+        ALOGW("Unexpected HDCP level changed for internal display: connected=%d, max=%d for "
+              "display=%" PRIu64,
+              connectedLevel, maxLevel, hwcDisplayId);
+        return;
+    }
+
+    static_cast<void>(mScheduler->schedule([this, displayId = *idOpt, connectedLevel, maxLevel]() {
+        if (const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayId))) {
+            Mutex::Autolock lock(mStateLock);
+            display->setSecure(connectedLevel >= 2 /* HDCP_V1 */);
+        }
+        mScheduler->onHdcpLevelsChanged(scheduler::Cycle::Render, displayId, connectedLevel,
+                                        maxLevel);
+    }));
+}
+
 std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData(
         BufferData& bufferData, const char* layerName, uint64_t transactionId) {
     if (bufferData.buffer &&
@@ -8846,7 +9150,7 @@
                 Mutex::Autolock lock(mStateLock);
                 createEffectLayer(mirrorArgs, &unused, &childMirror);
                 MUTEX_ALIAS(mStateLock, childMirror->mFlinger->mStateLock);
-                childMirror->setClonedChild(layer->createClone(childMirror->getSequence()));
+                childMirror->setClonedChild(layer->createClone());
                 childMirror->reparent(mirrorDisplay.rootHandle);
             }
             // lock on mStateLock needs to be released before binder handle gets destroyed
@@ -8906,7 +9210,7 @@
             snapshots[i] = std::move(layerFE->mSnapshot);
         }
     }
-    if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
+    if (!mLayerLifecycleManagerEnabled) {
         for (auto [layer, layerFE] : layers) {
             layer->updateLayerSnapshot(std::move(layerFE->mSnapshot));
         }
@@ -8919,7 +9223,8 @@
     if (mLayerLifecycleManagerEnabled) {
         nsecs_t currentTime = systemTime();
         mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
+                        kMainThreadContext) {
                     if (cursorOnly &&
                         snapshot->compositionType !=
                                 aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
@@ -8942,7 +9247,7 @@
                     layers.emplace_back(legacyLayer.get(), layerFE.get());
                 });
     }
-    if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
+    if (!mLayerLifecycleManagerEnabled) {
         auto moveSnapshots = [&layers, &refreshArgs, cursorOnly](Layer* layer) {
             if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
                 if (cursorOnly &&
@@ -8980,11 +9285,12 @@
         std::optional<ui::LayerStack> layerStack, uint32_t uid,
         std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
                 snapshotFilterFn) {
-    return [&, layerStack, uid]() {
+    return [&, layerStack, uid]() FTL_FAKE_GUARD(kMainThreadContext) {
         std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
         bool stopTraversal = false;
         mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
+                        kMainThreadContext) {
                     if (stopTraversal) {
                         return;
                     }
@@ -9006,7 +9312,7 @@
                                                     "Couldnt find layer object for %s",
                                                     snapshot->getDebugString().c_str());
                     Layer* legacyLayer = (it == mLegacyLayers.end()) ? nullptr : it->second.get();
-                    sp<LayerFE> layerFE = getFactory().createLayerFE(snapshot->name);
+                    sp<LayerFE> layerFE = getFactory().createLayerFE(snapshot->name, legacyLayer);
                     layerFE->mSnapshot = std::make_unique<frontend::LayerSnapshot>(*snapshot);
                     layers.emplace_back(legacyLayer, std::move(layerFE));
                 });
@@ -9019,7 +9325,8 @@
 SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional<ui::LayerStack> layerStack,
                                                 uint32_t uid,
                                                 std::unordered_set<uint32_t> excludeLayerIds) {
-    return [&, layerStack, uid, excludeLayerIds = std::move(excludeLayerIds)]() {
+    return [&, layerStack, uid,
+            excludeLayerIds = std::move(excludeLayerIds)]() FTL_FAKE_GUARD(kMainThreadContext) {
         if (excludeLayerIds.empty()) {
             auto getLayerSnapshotsFn =
                     getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr);
@@ -9061,7 +9368,7 @@
                                                 bool childrenOnly,
                                                 const std::optional<FloatRect>& parentCrop) {
     return [&, rootLayerId, uid, excludeLayerIds = std::move(excludeLayerIds), childrenOnly,
-            parentCrop]() {
+            parentCrop]() FTL_FAKE_GUARD(kMainThreadContext) {
         auto root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly);
         frontend::LayerSnapshotBuilder::Args
                 args{.root = root,
@@ -9241,35 +9548,35 @@
     }
 }
 
-binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure,
-                                                  float requestedRefreshRate,
-                                                  sp<IBinder>* outDisplay) {
+binder::Status SurfaceComposerAIDL::createVirtualDisplay(const std::string& displayName,
+                                                         bool isSecure, const std::string& uniqueId,
+                                                         float requestedRefreshRate,
+                                                         sp<IBinder>* outDisplay) {
     status_t status = checkAccessPermission();
     if (status != OK) {
         return binderStatusFromStatusT(status);
     }
-    String8 displayName8 = String8::format("%s", displayName.c_str());
-    *outDisplay = mFlinger->createDisplay(displayName8, secure, requestedRefreshRate);
+    *outDisplay =
+            mFlinger->createVirtualDisplay(displayName, isSecure, uniqueId, requestedRefreshRate);
     return binder::Status::ok();
 }
 
-binder::Status SurfaceComposerAIDL::destroyDisplay(const sp<IBinder>& display) {
+binder::Status SurfaceComposerAIDL::destroyVirtualDisplay(const sp<IBinder>& displayToken) {
     status_t status = checkAccessPermission();
     if (status != OK) {
         return binderStatusFromStatusT(status);
     }
-    mFlinger->destroyDisplay(display);
-    return binder::Status::ok();
+    return binder::Status::fromStatusT(mFlinger->destroyVirtualDisplay(displayToken));
 }
 
 binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) {
     std::vector<PhysicalDisplayId> physicalDisplayIds = mFlinger->getPhysicalDisplayIds();
     std::vector<int64_t> displayIds;
     displayIds.reserve(physicalDisplayIds.size());
-    for (auto item : physicalDisplayIds) {
-        displayIds.push_back(static_cast<int64_t>(item.value));
+    for (const auto id : physicalDisplayIds) {
+        displayIds.push_back(static_cast<int64_t>(id.value));
     }
-    *outDisplayIds = displayIds;
+    *outDisplayIds = std::move(displayIds);
     return binder::Status::ok();
 }
 
@@ -9572,11 +9879,18 @@
         std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
         mFlinger->captureDisplay(*id, args, captureListener);
     } else {
+        ALOGD("Permission denied to captureDisplayById");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
     }
     return binderStatusFromStatusT(NO_ERROR);
 }
 
+binder::Status SurfaceComposerAIDL::captureLayersSync(const LayerCaptureArgs& args,
+                                                      ScreenCaptureResults* outResults) {
+    *outResults = mFlinger->captureLayersSync(args);
+    return binderStatusFromStatusT(NO_ERROR);
+}
+
 binder::Status SurfaceComposerAIDL::captureLayers(
         const LayerCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
     mFlinger->captureLayers(args, captureListener);
@@ -9611,22 +9925,6 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) {
-    if (!outLayers) {
-        return binderStatusFromStatusT(UNEXPECTED_NULL);
-    }
-
-    IPCThreadState* ipc = IPCThreadState::self();
-    const int pid = ipc->getCallingPid();
-    const int uid = ipc->getCallingUid();
-    if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) {
-        ALOGE("Layer debug info permission denied for pid=%d, uid=%d", pid, uid);
-        return binderStatusFromStatusT(PERMISSION_DENIED);
-    }
-    status_t status = mFlinger->getLayerDebugInfo(outLayers);
-    return binderStatusFromStatusT(status);
-}
-
 binder::Status SurfaceComposerAIDL::getCompositionPreference(gui::CompositionPreference* outPref) {
     ui::Dataspace dataspace;
     ui::PixelFormat pixelFormat;
@@ -10038,6 +10336,11 @@
     return gui::getSchedulingPolicy(outPolicy);
 }
 
+binder::Status SurfaceComposerAIDL::notifyShutdown() {
+    TransactionTraceWriter::getInstance().invoke("systemShutdown_", /* overwrite= */ false);
+    return ::android::binder::Status::ok();
+}
+
 status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) {
     if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) {
         IPCThreadState* ipc = IPCThreadState::self();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 61360a7..8a39016 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -38,7 +38,6 @@
 #include <gui/FrameTimestamps.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/ITransactionCompletedListener.h>
-#include <gui/LayerDebugInfo.h>
 #include <gui/LayerState.h>
 #include <layerproto/LayerProtoHeader.h>
 #include <math/mat4.h>
@@ -66,6 +65,7 @@
 #include <ui/FenceResult.h>
 
 #include <common/FlagManager.h>
+#include "Display/DisplayModeController.h"
 #include "Display/PhysicalDisplay.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
@@ -82,7 +82,6 @@
 #include "MutexUtils.h"
 #include "Scheduler/ISchedulerCallback.h"
 #include "Scheduler/RefreshRateSelector.h"
-#include "Scheduler/RefreshRateStats.h"
 #include "Scheduler/Scheduler.h"
 #include "SurfaceFlingerFactory.h"
 #include "ThreadContext.h"
@@ -90,6 +89,7 @@
 #include "Tracing/TransactionTracing.h"
 #include "TransactionCallbackInvoker.h"
 #include "TransactionState.h"
+#include "Utils/OnceFuture.h"
 
 #include <atomic>
 #include <cstdint>
@@ -192,6 +192,9 @@
     Always,
 };
 
+struct DisplayRenderAreaBuilder;
+struct LayerRenderAreaBuilder;
+
 using DisplayColorSetting = compositionengine::OutputColorSetting;
 
 class SurfaceFlinger : public BnSurfaceComposer,
@@ -200,8 +203,7 @@
                        private HWC2::ComposerCallback,
                        private ICompositor,
                        private scheduler::ISchedulerCallback,
-                       private compositionengine::ICEPowerCallback,
-                       private scheduler::IVsyncTrackerCallback {
+                       private compositionengine::ICEPowerCallback {
 public:
     struct SkipInitializationTag {};
 
@@ -384,7 +386,7 @@
 
     using TransactionSchedule = scheduler::TransactionSchedule;
     using GetLayerSnapshotsFunction = std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>;
-    using RenderAreaFuture = ftl::Future<std::unique_ptr<RenderArea>>;
+    using RenderAreaBuilderVariant = std::variant<DisplayRenderAreaBuilder, LayerRenderAreaBuilder>;
     using DumpArgs = Vector<String16>;
     using Dumper = std::function<void(const DumpArgs&, bool asProto, std::string&)>;
 
@@ -510,10 +512,7 @@
         return lockedDumper(std::bind(dump, this, _1, _2, _3));
     }
 
-    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
-    Dumper mainThreadDumper(F dump) {
-        using namespace std::placeholders;
-        Dumper dumper = std::bind(dump, this, _3);
+    Dumper mainThreadDumperImpl(Dumper dumper) {
         return [this, dumper](const DumpArgs& args, bool asProto, std::string& result) -> void {
             mScheduler
                     ->schedule(
@@ -523,21 +522,35 @@
         };
     }
 
+    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+    Dumper mainThreadDumper(F dump) {
+        using namespace std::placeholders;
+        return mainThreadDumperImpl(std::bind(dump, this, _3));
+    }
+
+    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+    Dumper argsMainThreadDumper(F dump) {
+        using namespace std::placeholders;
+        return mainThreadDumperImpl(std::bind(dump, this, _1, _3));
+    }
+
     // Maximum allowed number of display frames that can be set through backdoor
     static const int MAX_ALLOWED_DISPLAY_FRAMES = 2048;
 
     static const size_t MAX_LAYERS = 4096;
 
-    // Implements IBinder.
-    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override;
-    status_t dump(int fd, const Vector<String16>& args) override { return priorityDump(fd, args); }
-    bool callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache = true)
+    static bool callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache = true)
             EXCLUDES(mStateLock);
 
-    // Implements ISurfaceComposer
-    sp<IBinder> createDisplay(const String8& displayName, bool secure,
-                              float requestedRefreshRate = 0.0f);
-    void destroyDisplay(const sp<IBinder>& displayToken);
+    // IBinder overrides:
+    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override;
+    status_t dump(int fd, const Vector<String16>& args) override { return priorityDump(fd, args); }
+
+    // ISurfaceComposer implementation:
+    sp<IBinder> createVirtualDisplay(const std::string& displayName, bool isSecure,
+                                     const std::string& uniqueId,
+                                     float requestedRefreshRate = 0.0f);
+    status_t destroyVirtualDisplay(const sp<IBinder>& displayToken);
     std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const EXCLUDES(mStateLock) {
         Mutex::Autolock lock(mStateLock);
         return getPhysicalDisplayIdsLocked();
@@ -552,7 +565,7 @@
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
             uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) override;
     void bootFinished();
-    virtual status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
+    status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
     sp<IDisplayEventConnection> createDisplayEventConnection(
             gui::ISurfaceComposer::VsyncSource vsyncSource =
                     gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
@@ -561,6 +574,7 @@
 
     void captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
     void captureDisplay(DisplayId, const CaptureArgs&, const sp<IScreenCaptureListener>&);
+    ScreenCaptureResults captureLayersSync(const LayerCaptureArgs&);
     void captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
 
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats);
@@ -590,7 +604,6 @@
     status_t overrideHdrTypes(const sp<IBinder>& displayToken,
                               const std::vector<ui::Hdr>& hdrTypes);
     status_t onPullAtom(const int32_t atomId, std::vector<uint8_t>* pulledData, bool* success);
-    status_t getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers);
     status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat,
                                       ui::Dataspace* outWideColorGamutDataspace,
                                       ui::PixelFormat* outWideColorGamutPixelFormat) const;
@@ -654,7 +667,9 @@
     status_t getStalledTransactionInfo(
             int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result);
 
-    // Implements IBinder::DeathRecipient.
+    void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel);
+
+    // IBinder::DeathRecipient overrides:
     void binderDied(const wp<IBinder>& who) override;
 
     // HWC2::ComposerCallback overrides:
@@ -684,14 +699,14 @@
     void kernelTimerChanged(bool expired) override;
     void triggerOnFrameRateOverridesChanged() override;
     void onChoreographerAttached() override;
+    void onExpectedPresentTimePosted(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
+                                     Fps renderRate) override;
+    void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) override
+            REQUIRES(kMainThreadContext);
 
     // ICEPowerCallback overrides:
     void notifyCpuLoadUp() override;
 
-    // IVsyncTrackerCallback overrides
-    void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
-                          Fps renderRate) override;
-
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
 
@@ -700,7 +715,7 @@
     // Get the controller and timeout that will help decide how the kernel idle timer will be
     // configured and what value to use as the timeout.
     std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
-            getKernelIdleTimerProperties(DisplayId) REQUIRES(mStateLock);
+            getKernelIdleTimerProperties(PhysicalDisplayId) REQUIRES(mStateLock);
     // Updates the kernel idle timer either through HWC or through sysprop
     // depending on which controller is provided
     void updateKernelIdleTimer(std::chrono::milliseconds timeoutMs, KernelIdleTimerController,
@@ -717,7 +732,7 @@
     // Show hdr sdr ratio overlay
     bool mHdrSdrRatioOverlay = false;
 
-    void setDesiredMode(display::DisplayModeRequest&&, bool force = false) REQUIRES(mStateLock);
+    void setDesiredMode(display::DisplayModeRequest&&) REQUIRES(mStateLock);
 
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
                                        Fps maxFps);
@@ -742,13 +757,9 @@
             const sp<DisplayDevice>&, const scheduler::RefreshRateSelector::PolicyVariant&)
             EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
 
-    bool shouldApplyRefreshRateSelectorPolicy(const DisplayDevice&) const
-            REQUIRES(mStateLock, kMainThreadContext);
-
     // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter.
     status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId,
-                                            const scheduler::RefreshRateSelector&,
-                                            bool force = false)
+                                            const scheduler::RefreshRateSelector&)
             REQUIRES(mStateLock, kMainThreadContext);
 
     void commitTransactionsLegacy() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
@@ -763,30 +774,30 @@
     void updateLayerGeometry();
     void updateLayerMetadataSnapshot();
     std::vector<std::pair<Layer*, LayerFE*>> moveSnapshotsToCompositionArgs(
-            compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly);
+            compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly)
+            REQUIRES(kMainThreadContext);
     void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
-                                          const std::vector<std::pair<Layer*, LayerFE*>>& layers);
+                                          const std::vector<std::pair<Layer*, LayerFE*>>& layers)
+            REQUIRES(kMainThreadContext);
     // Return true if we must composite this frame
     bool updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
                                     bool& out) REQUIRES(kMainThreadContext);
     // Return true if we must composite this frame
     bool updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
                               bool& out) REQUIRES(kMainThreadContext);
-    void updateLayerHistory(nsecs_t now);
+    void updateLayerHistory(nsecs_t now) REQUIRES(kMainThreadContext);
     frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
 
-    void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime);
+    void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) REQUIRES(kMainThreadContext);
     void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
     void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
-                          std::vector<gui::DisplayInfo>& outDisplayInfos);
+                          std::vector<gui::DisplayInfo>& outDisplayInfos)
+            REQUIRES(kMainThreadContext);
     void commitInputWindowCommands() REQUIRES(mStateLock);
-    void updateCursorAsync();
+    void updateCursorAsync() REQUIRES(kMainThreadContext);
 
     void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
 
-    void resetPhaseConfiguration(Fps) REQUIRES(mStateLock, kMainThreadContext);
-    void updatePhaseConfiguration(Fps) REQUIRES(mStateLock);
-
     /*
      * Transactions
      */
@@ -799,7 +810,7 @@
                                const int64_t postTime, bool hasListenerCallbacks,
                                const std::vector<ListenerCallbacks>& listenerCallbacks,
                                int originPid, int originUid, uint64_t transactionId)
-            REQUIRES(mStateLock);
+            REQUIRES(mStateLock, kMainThreadContext);
     // Flush pending transactions that were presented after desiredPresentTime.
     // For test only
     bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext);
@@ -809,8 +820,8 @@
             REQUIRES(kMainThreadContext, mStateLock);
 
     // Returns true if there is at least one transaction that needs to be flushed
-    bool transactionFlushNeeded();
-    void addTransactionReadyFilters();
+    bool transactionFlushNeeded() REQUIRES(kMainThreadContext);
+    void addTransactionReadyFilters() REQUIRES(kMainThreadContext);
     TransactionHandler::TransactionReadiness transactionReadyTimelineCheck(
             const TransactionHandler::TransactionFlushState& flushState)
             REQUIRES(kMainThreadContext);
@@ -827,7 +838,7 @@
     uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&,
                                           int64_t desiredPresentTime, bool isAutoTimestamp,
                                           int64_t postTime, uint64_t transactionId)
-            REQUIRES(mStateLock);
+            REQUIRES(mStateLock, kMainThreadContext);
     uint32_t getTransactionFlags() const;
 
     // Sets the masked bits, and schedules a commit if needed.
@@ -843,7 +854,7 @@
     static LatchUnsignaledConfig getLatchUnsignaledConfig();
     bool shouldLatchUnsignaled(const layer_state_t&, size_t numStates, bool firstTransaction) const;
     bool applyTransactionsLocked(std::vector<TransactionState>& transactions, VsyncId)
-            REQUIRES(mStateLock);
+            REQUIRES(mStateLock, kMainThreadContext);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
     uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
             REQUIRES(mStateLock);
@@ -876,22 +887,38 @@
     // Traverse through all the layers and compute and cache its bounds.
     void computeLayerBounds();
 
-    // Boot animation, on/off animations and screen capture
-    void startBootAnim();
+    // Creates a promise for a future release fence for a layer. This allows for
+    // the layer to keep track of when its buffer can be released.
+    void attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, ui::LayerStack layerStack);
 
-    void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize,
-                             ui::PixelFormat, bool allowProtected, bool grayscale,
-                             const sp<IScreenCaptureListener>&);
-    ftl::SharedFuture<FenceResult> captureScreenCommon(
-            RenderAreaFuture, GetLayerSnapshotsFunction,
+    // Checks if a protected layer exists in a list of layers.
+    bool layersHasProtectedLayer(const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
+
+    void captureScreenCommon(RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
+                             ui::Size bufferSize, ui::PixelFormat, bool allowProtected,
+                             bool grayscale, const sp<IScreenCaptureListener>&);
+
+    ftl::SharedFuture<FenceResult> captureScreenshot(
+            RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, bool isProtected, const sp<IScreenCaptureListener>&);
+
+    // Overloaded version of renderScreenImpl that is used when layer snapshots have
+    // not yet been captured, and thus cannot yet be passed in as a parameter.
+    // Needed for TestableSurfaceFlinger.
     ftl::SharedFuture<FenceResult> renderScreenImpl(
-            std::shared_ptr<const RenderArea>, GetLayerSnapshotsFunction,
+            std::unique_ptr<const RenderArea>, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock)
             REQUIRES(kMainThreadContext);
 
+    ftl::SharedFuture<FenceResult> renderScreenImpl(
+            std::unique_ptr<const RenderArea>,
+            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
+            bool grayscale, bool isProtected, ScreenCaptureResults&,
+            std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers) EXCLUDES(mStateLock)
+            REQUIRES(kMainThreadContext);
+
     // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a
     // matching ownerUid
     void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid,
@@ -906,7 +933,8 @@
      * Display and layer stack management
      */
 
-    // Called during boot, and restart after system_server death.
+    // Called during boot and restart after system_server death, setting the stage for bootanimation
+    // before DisplayManager takes over.
     void initializeDisplays() REQUIRES(kMainThreadContext);
 
     sp<const DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) const
@@ -965,8 +993,7 @@
         return getDefaultDisplayDeviceLocked();
     }
 
-    using DisplayDeviceAndSnapshot =
-            std::pair<sp<DisplayDevice>, display::PhysicalDisplay::SnapshotRef>;
+    using DisplayDeviceAndSnapshot = std::pair<sp<DisplayDevice>, display::DisplaySnapshotRef>;
 
     // Combinator for ftl::Optional<PhysicalDisplay>::and_then.
     auto getDisplayDeviceAndSnapshot() REQUIRES(mStateLock) {
@@ -1035,10 +1062,13 @@
     bool configureLocked() REQUIRES(mStateLock) REQUIRES(kMainThreadContext)
             EXCLUDES(mHotplugMutex);
 
-    // Returns a string describing the hotplug, or nullptr if it was rejected.
-    const char* processHotplug(PhysicalDisplayId, hal::HWDisplayId, bool connected,
-                               DisplayIdentificationInfo&&) REQUIRES(mStateLock)
-            REQUIRES(kMainThreadContext);
+    // Returns the active mode ID, or nullopt on hotplug failure.
+    std::optional<DisplayModeId> processHotplugConnect(PhysicalDisplayId, hal::HWDisplayId,
+                                                       DisplayIdentificationInfo&&,
+                                                       const char* displayString)
+            REQUIRES(mStateLock, kMainThreadContext);
+    void processHotplugDisconnect(PhysicalDisplayId, const char* displayString)
+            REQUIRES(mStateLock, kMainThreadContext);
 
     sp<DisplayDevice> setupNewDisplayDeviceInternal(
             const wp<IBinder>& displayToken,
@@ -1054,7 +1084,6 @@
                                const DisplayDeviceState& drawingState)
             REQUIRES(mStateLock, kMainThreadContext);
 
-    void dispatchDisplayHotplugEvent(PhysicalDisplayId, bool connected);
     void dispatchDisplayModeChangeEvent(PhysicalDisplayId, const scheduler::FrameRateMode&)
             REQUIRES(mStateLock);
 
@@ -1116,9 +1145,10 @@
     void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock);
 
     void appendSfConfigString(std::string& result) const;
-    void listLayersLocked(std::string& result) const;
-    void dumpStatsLocked(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
-    void clearStatsLocked(const DumpArgs& args, std::string& result);
+    void listLayers(std::string& result) const REQUIRES(kMainThreadContext);
+    void dumpStats(const DumpArgs& args, std::string& result) const
+            REQUIRES(mStateLock, kMainThreadContext);
+    void clearStats(const DumpArgs& args, std::string& result) REQUIRES(kMainThreadContext);
     void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const;
     void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
     void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext);
@@ -1136,7 +1166,8 @@
     void dumpFrontEnd(std::string& result) REQUIRES(kMainThreadContext);
     void dumpVisibleFrontEnd(std::string& result) REQUIRES(mStateLock, kMainThreadContext);
 
-    perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
+    perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const
+            REQUIRES(kMainThreadContext);
     void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
                                   uint32_t traceFlags = LayerTracing::TRACE_ALL) const;
     google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> dumpDisplayProto() const;
@@ -1184,12 +1215,19 @@
 
     ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
             REQUIRES(mStateLock);
-    void traverseLegacyLayers(const LayerVector::Visitor& visitor) const;
+    void traverseLegacyLayers(const LayerVector::Visitor& visitor) const
+            REQUIRES(kMainThreadContext);
+
+    void initBootProperties();
     void initTransactionTraceWriter();
-    sp<StartPropertySetThread> mStartPropertySetThread;
+
     surfaceflinger::Factory& mFactory;
     pid_t mPid;
-    std::future<void> mRenderEnginePrimeCacheFuture;
+
+    // TODO: b/328459745 - Encapsulate in a SystemProperties object.
+    utils::OnceFuture mInitBootPropsFuture;
+
+    utils::OnceFuture mRenderEnginePrimeCacheFuture;
 
     // mStateLock has conventions related to the current thread, because only
     // the main thread should modify variables protected by mStateLock.
@@ -1215,12 +1253,6 @@
     float mGlobalSaturationFactor = 1.0f;
     mat4 mClientColorMatrix;
 
-    size_t mMaxGraphicBufferProducerListSize = MAX_LAYERS;
-    // If there are more GraphicBufferProducers tracked by SurfaceFlinger than
-    // this threshold, then begin logging.
-    size_t mGraphicBufferProducerListSizeLogThreshold =
-            static_cast<size_t>(0.95 * static_cast<double>(MAX_LAYERS));
-
     // protected by mStateLock (but we could use another lock)
     bool mLayersRemoved = false;
     bool mLayersAdded = false;
@@ -1231,6 +1263,7 @@
     // constant members (no synchronization needed for access)
     const nsecs_t mBootTime = systemTime();
     bool mIsUserBuild = true;
+    bool mHasReliablePresentFences = false;
 
     // Can only accessed from the main thread, these members
     // don't need synchronization
@@ -1277,6 +1310,7 @@
         hal::Connection connection = hal::Connection::INVALID;
     };
 
+    bool mIsHdcpViaNegVsync = false;
     bool mIsHotplugErrViaNegVsync = false;
 
     std::mutex mHotplugMutex;
@@ -1295,6 +1329,8 @@
     // reads from ISchedulerCallback::requestDisplayModes may happen concurrently.
     std::atomic<PhysicalDisplayId> mActiveDisplayId GUARDED_BY(mStateLock);
 
+    display::DisplayModeController mDisplayModeController;
+
     struct {
         DisplayIdGenerator<GpuVirtualDisplayId> gpu;
         std::optional<DisplayIdGenerator<HalVirtualDisplayId>> hal;
@@ -1365,17 +1401,8 @@
 
     const std::string mHwcServiceName;
 
-    /*
-     * Scheduler
-     */
     std::unique_ptr<scheduler::Scheduler> mScheduler;
-    scheduler::ConnectionHandle mAppConnectionHandle;
-    scheduler::ConnectionHandle mSfConnectionHandle;
 
-    // Stores phase offsets configured per refresh rate.
-    std::unique_ptr<scheduler::VsyncConfiguration> mVsyncConfiguration;
-
-    std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats;
     scheduler::PresentLatencyTracker mPresentLatencyTracker GUARDED_BY(kMainThreadContext);
 
     bool mLumaSampling = true;
@@ -1467,39 +1494,58 @@
     bool mPowerHintSessionEnabled;
 
     bool mLayerLifecycleManagerEnabled = false;
-    bool mLegacyFrontEndEnabled = true;
+    // Whether a display should be turned on when initialized
+    bool mSkipPowerOnForQuiescent;
 
-    frontend::LayerLifecycleManager mLayerLifecycleManager;
-    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
-    frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
+    frontend::LayerLifecycleManager mLayerLifecycleManager GUARDED_BY(kMainThreadContext);
+    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder GUARDED_BY(kMainThreadContext);
+    frontend::LayerSnapshotBuilder mLayerSnapshotBuilder GUARDED_BY(kMainThreadContext);
 
-    std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles;
-    std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
-    std::vector<LayerCreationArgs> mNewLayerArgs;
+    std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles GUARDED_BY(mCreatedLayersLock);
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers
+            GUARDED_BY(mCreatedLayersLock);
+    std::vector<LayerCreationArgs> mNewLayerArgs GUARDED_BY(mCreatedLayersLock);
     // These classes do not store any client state but help with managing transaction callbacks
     // and stats.
-    std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers;
+    std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers GUARDED_BY(kMainThreadContext);
 
-    TransactionHandler mTransactionHandler;
-    ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
-    bool mFrontEndDisplayInfosChanged = false;
+    TransactionHandler mTransactionHandler GUARDED_BY(kMainThreadContext);
+    ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos
+            GUARDED_BY(kMainThreadContext);
+    bool mFrontEndDisplayInfosChanged GUARDED_BY(kMainThreadContext) = false;
 
     // WindowInfo ids visible during the last commit.
-    std::unordered_set<int32_t> mVisibleWindowIds;
+    std::unordered_set<int32_t> mVisibleWindowIds GUARDED_BY(kMainThreadContext);
 
     // Mirroring
     // Map of displayid to mirrorRoot
     ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug;
 
     // NotifyExpectedPresentHint
+    enum class NotifyExpectedPresentHintStatus {
+        // Represents that framework can start sending hint if required.
+        Start,
+        // Represents that the hint is already sent.
+        Sent,
+        // Represents that the hint will be scheduled with a new frame.
+        ScheduleOnPresent,
+        // Represents that a hint will be sent instantly by scheduling on the main thread.
+        ScheduleOnTx
+    };
     struct NotifyExpectedPresentData {
-        // lastExpectedPresentTimestamp is read and write from multiple threads such as
-        // main thread, EventThread, MessageQueue. And is atomic for that reason.
-        std::atomic<TimePoint> lastExpectedPresentTimestamp{};
+        TimePoint lastExpectedPresentTimestamp{};
         Fps lastFrameInterval{};
+        // hintStatus is read and write from multiple threads such as
+        // main thread, EventThread. And is atomic for that reason.
+        std::atomic<NotifyExpectedPresentHintStatus> hintStatus =
+                NotifyExpectedPresentHintStatus::Start;
     };
     std::unordered_map<PhysicalDisplayId, NotifyExpectedPresentData> mNotifyExpectedPresentMap;
-
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) override
+            REQUIRES(kMainThreadContext);
+    void scheduleNotifyExpectedPresentHint(PhysicalDisplayId displayId,
+                                           VsyncId vsyncId = VsyncId{
+                                                   FrameTimelineInfo::INVALID_VSYNC_ID});
     void notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
                                          TimePoint expectedPresentTime, Fps frameInterval,
                                          std::optional<Period> timeoutOpt);
@@ -1513,7 +1559,7 @@
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
 public:
-    SurfaceComposerAIDL(sp<SurfaceFlinger> sf) : mFlinger(std::move(sf)) {}
+    explicit SurfaceComposerAIDL(sp<SurfaceFlinger> sf) : mFlinger(std::move(sf)) {}
 
     binder::Status bootFinished() override;
     binder::Status createDisplayEventConnection(
@@ -1521,9 +1567,10 @@
             const sp<IBinder>& layerHandle,
             sp<gui::IDisplayEventConnection>* outConnection) override;
     binder::Status createConnection(sp<gui::ISurfaceComposerClient>* outClient) override;
-    binder::Status createDisplay(const std::string& displayName, bool secure,
-                                 float requestedRefreshRate, sp<IBinder>* outDisplay) override;
-    binder::Status destroyDisplay(const sp<IBinder>& display) override;
+    binder::Status createVirtualDisplay(const std::string& displayName, bool isSecure,
+                                        const std::string& uniqueId, float requestedRefreshRate,
+                                        sp<IBinder>* outDisplay) override;
+    binder::Status destroyVirtualDisplay(const sp<IBinder>& displayToken) override;
     binder::Status getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) override;
     binder::Status getPhysicalDisplayToken(int64_t displayId, sp<IBinder>* outDisplay) override;
     binder::Status setPowerMode(const sp<IBinder>& display, int mode) override;
@@ -1558,6 +1605,7 @@
                                       const sp<IScreenCaptureListener>&) override;
     binder::Status captureLayers(const LayerCaptureArgs&,
                                  const sp<IScreenCaptureListener>&) override;
+    binder::Status captureLayersSync(const LayerCaptureArgs&, ScreenCaptureResults* results);
 
     // TODO(b/239076119): Remove deprecated AIDL.
     [[deprecated]] binder::Status clearAnimationFrameStats() override {
@@ -1570,7 +1618,6 @@
     binder::Status overrideHdrTypes(const sp<IBinder>& display,
                                     const std::vector<int32_t>& hdrTypes) override;
     binder::Status onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) override;
-    binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) override;
     binder::Status getCompositionPreference(gui::CompositionPreference* outPref) override;
     binder::Status getDisplayedContentSamplingAttributes(
             const sp<IBinder>& display, gui::ContentSamplingAttributes* outAttrs) override;
@@ -1634,6 +1681,7 @@
     binder::Status getStalledTransactionInfo(
             int pid, std::optional<gui::StalledTransactionInfo>* outInfo) override;
     binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override;
+    binder::Status notifyShutdown() override;
 
 private:
     static const constexpr bool kUsePermissionCache = true;
@@ -1644,7 +1692,7 @@
                                               gui::DynamicDisplayInfo*& outInfo);
 
 private:
-    sp<SurfaceFlinger> mFlinger;
+    const sp<SurfaceFlinger> mFlinger;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index 7e6894d..b1d8ba9 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -26,7 +26,6 @@
 #include "FrameTracer/FrameTracer.h"
 #include "Layer.h"
 #include "NativeWindowSurface.h"
-#include "StartPropertySetThread.h"
 #include "SurfaceFlingerDefaultFactory.h"
 #include "SurfaceFlingerProperties.h"
 
@@ -53,11 +52,6 @@
     }
 }
 
-sp<StartPropertySetThread> DefaultFactory::createStartPropertySetThread(
-        bool timestampPropertyValue) {
-    return sp<StartPropertySetThread>::make(timestampPropertyValue);
-}
-
 sp<DisplayDevice> DefaultFactory::createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) {
     return sp<DisplayDevice>::make(creationArgs);
 }
@@ -91,7 +85,7 @@
     return sp<Layer>::make(args);
 }
 
-sp<LayerFE> DefaultFactory::createLayerFE(const std::string& layerName) {
+sp<LayerFE> DefaultFactory::createLayerFE(const std::string& layerName, const Layer* /* owner */) {
     return sp<LayerFE>::make(layerName);
 }
 
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 2c6de0e..7ebf10f 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -29,7 +29,6 @@
     std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) override;
     std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps currentRefreshRate) override;
-    sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override;
     sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&) override;
     sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format,
                                           uint32_t layerCount, uint64_t usage,
@@ -42,7 +41,7 @@
     std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override;
     sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) override;
     sp<Layer> createEffectLayer(const LayerCreationArgs& args) override;
-    sp<LayerFE> createLayerFE(const std::string& layerName) override;
+    sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* owner) override;
     std::unique_ptr<FrameTracer> createFrameTracer() override;
     std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
             std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index f310c4a..c7d1fa0 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -39,7 +39,6 @@
 class IGraphicBufferProducer;
 class Layer;
 class LayerFE;
-class StartPropertySetThread;
 class SurfaceFlinger;
 class TimeStats;
 
@@ -71,8 +70,6 @@
     virtual std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps currentRefreshRate) = 0;
 
-    virtual sp<StartPropertySetThread> createStartPropertySetThread(
-            bool timestampPropertyValue) = 0;
     virtual sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&) = 0;
     virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height,
                                                   PixelFormat format, uint32_t layerCount,
@@ -88,7 +85,7 @@
 
     virtual sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) = 0;
     virtual sp<Layer> createEffectLayer(const LayerCreationArgs& args) = 0;
-    virtual sp<LayerFE> createLayerFE(const std::string& layerName) = 0;
+    virtual sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* owner) = 0;
     virtual std::unique_ptr<FrameTracer> createFrameTracer() = 0;
     virtual std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
             std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) = 0;
diff --git a/services/surfaceflinger/TimeStats/Android.bp b/services/surfaceflinger/TimeStats/Android.bp
index c3141be..a631074 100644
--- a/services/surfaceflinger/TimeStats/Android.bp
+++ b/services/surfaceflinger/TimeStats/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_defaults {
diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/Android.bp b/services/surfaceflinger/TimeStats/timestatsatomsproto/Android.bp
index 0cf086f..be65510 100644
--- a/services/surfaceflinger/TimeStats/timestatsatomsproto/Android.bp
+++ b/services/surfaceflinger/TimeStats/timestatsatomsproto/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_library {
@@ -33,4 +34,4 @@
         "-Wno-undef",
         "-Wno-unused-parameter",
     ],
-}
\ No newline at end of file
+}
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/Android.bp b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
index 972edaa..e097da3 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_library {
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index 2dc89b5..fc496b2 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -247,7 +247,8 @@
         proto.set_auto_refresh(layer.autoRefresh);
     }
     if (layer.what & layer_state_t::eTrustedOverlayChanged) {
-        proto.set_is_trusted_overlay(layer.isTrustedOverlay);
+        proto.set_is_trusted_overlay(layer.trustedOverlay == gui::TrustedOverlay::ENABLED);
+        // TODO(b/339701674) update protos
     }
     if (layer.what & layer_state_t::eBufferCropChanged) {
         LayerProtoHelper::writeToProto(layer.bufferCrop, proto.mutable_buffer_crop());
@@ -515,7 +516,8 @@
         layer.autoRefresh = proto.auto_refresh();
     }
     if (proto.what() & layer_state_t::eTrustedOverlayChanged) {
-        layer.isTrustedOverlay = proto.is_trusted_overlay();
+        layer.trustedOverlay = proto.is_trusted_overlay() ? gui::TrustedOverlay::ENABLED
+                                                          : gui::TrustedOverlay::UNSET;
     }
     if (proto.what() & layer_state_t::eBufferCropChanged) {
         LayerProtoHelper::readFromProto(proto.buffer_crop(), layer.bufferCrop);
@@ -566,7 +568,7 @@
         const frontend::DisplayInfo& displayInfo, uint32_t layerStack) {
     perfetto::protos::DisplayInfo proto;
     proto.set_layer_stack(layerStack);
-    proto.set_display_id(displayInfo.info.displayId);
+    proto.set_display_id(displayInfo.info.displayId.val());
     proto.set_logical_width(displayInfo.info.logicalWidth);
     proto.set_logical_height(displayInfo.info.logicalHeight);
     asProto(proto.mutable_transform_inverse(), displayInfo.info.transform);
@@ -588,7 +590,7 @@
 frontend::DisplayInfo TransactionProtoParser::fromProto(
         const perfetto::protos::DisplayInfo& proto) {
     frontend::DisplayInfo displayInfo;
-    displayInfo.info.displayId = proto.display_id();
+    displayInfo.info.displayId = ui::LogicalDisplayId{proto.display_id()};
     displayInfo.info.logicalWidth = proto.logical_width();
     displayInfo.info.logicalHeight = proto.logical_height();
     fromProto2(displayInfo.info.transform, proto.transform_inverse());
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index cb96e05..7a0fb5e 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -219,6 +219,13 @@
     friend class Singleton<TransactionTracing>;
     std::function<void(const std::string& prefix, bool overwrite)> mWriterFunction =
             [](const std::string&, bool) {};
+    std::atomic<bool> mEnabled{true};
+
+    void doInvoke(const std::string& filename, bool overwrite) {
+        if (mEnabled) {
+            mWriterFunction(filename, overwrite);
+        }
+    };
 
 public:
     void setWriterFunction(
@@ -226,12 +233,15 @@
         mWriterFunction = std::move(function);
     }
     void invoke(const std::string& prefix, bool overwrite) {
-        mWriterFunction(TransactionTracing::getFilePath(prefix), overwrite);
+        doInvoke(TransactionTracing::getFilePath(prefix), overwrite);
     }
     /* pass in a complete file path for testing */
     void invokeForTest(const std::string& filename, bool overwrite) {
-        mWriterFunction(filename, overwrite);
+        doInvoke(filename, overwrite);
     }
+    /* hacky way to avoid generating traces when converting transaction trace to layers trace. */
+    void disable() { mEnabled.store(false); }
+    void enable() { mEnabled.store(true); }
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp
index 2ff09c3..8afca41 100644
--- a/services/surfaceflinger/Tracing/tools/Android.bp
+++ b/services/surfaceflinger/Tracing/tools/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_binary {
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index c2d1954..617ea2c 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -40,9 +40,24 @@
 namespace android {
 using namespace ftl::flag_operators;
 
+namespace {
+class ScopedTraceDisabler {
+public:
+    ScopedTraceDisabler() { TransactionTraceWriter::getInstance().disable(); }
+    ~ScopedTraceDisabler() { TransactionTraceWriter::getInstance().enable(); }
+};
+} // namespace
+
 bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& traceFile,
                                    std::uint32_t traceFlags, LayerTracing& layerTracing,
                                    bool onlyLastEntry) {
+    // We are generating the layers trace by replaying back a set of transactions. If the
+    // transactions have unexpected states, we may generate a transaction trace to debug
+    // the unexpected state. This is silly. So we disable it by poking the
+    // TransactionTraceWriter. This is really a hack since we should manage our depenecies a
+    // little better.
+    ScopedTraceDisabler fatalErrorTraceDisabler;
+
     if (traceFile.entry_size() == 0) {
         ALOGD("Trace file is empty");
         return false;
@@ -52,7 +67,7 @@
 
     // frontend
     frontend::LayerLifecycleManager lifecycleManager;
-    frontend::LayerHierarchyBuilder hierarchyBuilder{{}};
+    frontend::LayerHierarchyBuilder hierarchyBuilder;
     frontend::LayerSnapshotBuilder snapshotBuilder;
     ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos;
 
@@ -119,12 +134,10 @@
         lifecycleManager.applyTransactions(transactions, /*ignoreUnknownHandles=*/true);
         lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true);
 
-        if (lifecycleManager.getGlobalChanges().test(
-                    frontend::RequestedLayerState::Changes::Hierarchy)) {
-            hierarchyBuilder.update(lifecycleManager.getLayers(),
-                                    lifecycleManager.getDestroyedLayers());
-        }
+        // update hierarchy
+        hierarchyBuilder.update(lifecycleManager);
 
+        // update snapshots
         frontend::LayerSnapshotBuilder::Args args{.root = hierarchyBuilder.getHierarchy(),
                                                   .layerLifecycleManager = lifecycleManager,
                                                   .displays = displayInfos,
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 6a155c1..222ae30 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -25,10 +25,12 @@
 
 #include "TransactionCallbackInvoker.h"
 #include "BackgroundExecutor.h"
+#include "Utils/FenceUtils.h"
 
 #include <cinttypes>
 
 #include <binder/IInterface.h>
+#include <common/FlagManager.h>
 #include <utils/RefBase.h>
 
 namespace android {
@@ -128,33 +130,16 @@
     if (surfaceControl) {
         sp<Fence> prevFence = nullptr;
 
-        for (const auto& future : handle->previousReleaseFences) {
-            sp<Fence> currentFence = future.get().value_or(Fence::NO_FENCE);
-            if (prevFence == nullptr && currentFence->getStatus() != Fence::Status::Invalid) {
-                prevFence = std::move(currentFence);
-            } else if (prevFence != nullptr) {
-                // If both fences are signaled or both are unsignaled, we need to merge
-                // them to get an accurate timestamp.
-                if (prevFence->getStatus() != Fence::Status::Invalid &&
-                    prevFence->getStatus() == currentFence->getStatus()) {
-                    char fenceName[32] = {};
-                    snprintf(fenceName, 32, "%.28s", handle->name.c_str());
-                    sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, currentFence);
-                    if (mergedFence->isValid()) {
-                        prevFence = std::move(mergedFence);
-                    }
-                } else if (currentFence->getStatus() == Fence::Status::Unsignaled) {
-                    // If one fence has signaled and the other hasn't, the unsignaled
-                    // fence will approximately correspond with the correct timestamp.
-                    // There's a small race if both fences signal at about the same time
-                    // and their statuses are retrieved with unfortunate timing. However,
-                    // by this point, they will have both signaled and only the timestamp
-                    // will be slightly off; any dependencies after this point will
-                    // already have been met.
-                    prevFence = std::move(currentFence);
-                }
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            for (auto& future : handle->previousReleaseFences) {
+                mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+            }
+        } else {
+            for (const auto& future : handle->previousSharedReleaseFences) {
+                mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
             }
         }
+
         handle->previousReleaseFence = prevFence;
         handle->previousReleaseFences.clear();
 
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 245398f..cb7150b 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -46,7 +46,8 @@
     bool releasePreviousBuffer = false;
     std::string name;
     sp<Fence> previousReleaseFence;
-    std::vector<ftl::SharedFuture<FenceResult>> previousReleaseFences;
+    std::vector<ftl::Future<FenceResult>> previousReleaseFences;
+    std::vector<ftl::SharedFuture<FenceResult>> previousSharedReleaseFences;
     std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     nsecs_t latchTime = -1;
     std::optional<uint32_t> transformHint = std::nullopt;
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 31cd2d7..e5d6481 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -23,6 +23,7 @@
 #include "FrontEnd/LayerCreationArgs.h"
 #include "renderengine/ExternalTexture.h"
 
+#include <common/FlagManager.h>
 #include <gui/LayerState.h>
 #include <system/window.h>
 
@@ -86,7 +87,7 @@
     }
 
     template <typename Visitor>
-    void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) {
+    void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) NO_THREAD_SAFETY_ANALYSIS {
         for (auto state = states.begin(); state != states.end();) {
             if (state->state.hasBufferChanges() && state->externalTexture && state->state.surface) {
                 int result = visitor(*state);
@@ -108,9 +109,22 @@
 
         for (const auto& state : states) {
             const bool frameRateChanged = state.state.what & layer_state_t::eFrameRateChanged;
-            if (!frameRateChanged ||
-                state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
-                return true;
+            if (FlagManager::getInstance().vrr_bugfix_24q4()) {
+                const bool frameRateIsNoVote = frameRateChanged &&
+                        state.state.frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_NO_VOTE;
+                const bool frameRateCategoryChanged =
+                        state.state.what & layer_state_t::eFrameRateCategoryChanged;
+                const bool frameRateCategoryIsNoPreference = frameRateCategoryChanged &&
+                        state.state.frameRateCategory ==
+                                ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                if (!frameRateIsNoVote && !frameRateCategoryIsNoPreference) {
+                    return true;
+                }
+            } else {
+                if (!frameRateChanged ||
+                    state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
+                    return true;
+                }
             }
         }
 
diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h
index ee94217..62d2ebb 100644
--- a/services/surfaceflinger/Utils/Dumper.h
+++ b/services/surfaceflinger/Utils/Dumper.h
@@ -35,6 +35,8 @@
 
     void eol() { mOut += '\n'; }
 
+    std::string& out() { return mOut; }
+
     void dump(std::string_view name, std::string_view value = {}) {
         using namespace std::string_view_literals;
 
diff --git a/services/surfaceflinger/Utils/FenceUtils.h b/services/surfaceflinger/Utils/FenceUtils.h
new file mode 100644
index 0000000..f6f7006
--- /dev/null
+++ b/services/surfaceflinger/Utils/FenceUtils.h
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2024 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/Fence.h>
+
+namespace android {
+
+// TODO: measure if Fence::merge is cheaper
+inline void mergeFence(const char* debugName, sp<Fence>&& incomingFence, sp<Fence>& prevFence) {
+    if (prevFence == nullptr && incomingFence->getStatus() != Fence::Status::Invalid) {
+        prevFence = std::move(incomingFence);
+    } else if (prevFence != nullptr) {
+        // If both fences are signaled or both are unsignaled, we need to merge
+        // them to get an accurate timestamp.
+        if (prevFence->getStatus() != Fence::Status::Invalid &&
+            prevFence->getStatus() == incomingFence->getStatus()) {
+            char fenceName[32] = {};
+            snprintf(fenceName, 32, "%.28s", debugName);
+            sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, incomingFence);
+            if (mergedFence->isValid()) {
+                prevFence = std::move(mergedFence);
+            }
+        } else if (incomingFence->getStatus() == Fence::Status::Unsignaled) {
+            // If one fence has signaled and the other hasn't, the unsignaled
+            // fence will approximately correspond with the correct timestamp.
+            // There's a small race if both fences signal at about the same time
+            // and their statuses are retrieved with unfortunate timing. However,
+            // by this point, they will have both signaled and only the timestamp
+            // will be slightly off; any dependencies after this point will
+            // already have been met.
+            prevFence = std::move(incomingFence);
+        }
+    }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/Utils/OnceFuture.h b/services/surfaceflinger/Utils/OnceFuture.h
new file mode 100644
index 0000000..412038c
--- /dev/null
+++ b/services/surfaceflinger/Utils/OnceFuture.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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 <future>
+#include <mutex>
+
+#include <android-base/thread_annotations.h>
+
+namespace android::utils {
+
+// Allows a thread to `wait` for a future produced by a different thread. The future is returned by
+// the first call to a function `F` that multiple threads may `callOnce`. If no `callOnce` happens,
+// then `wait` does nothing. Otherwise, it blocks on the future, then destroys it, which resets the
+// `OnceFuture`.
+class OnceFuture {
+public:
+    template <typename F>
+    void callOnce(F f) {
+        std::lock_guard lock(mMutex);
+        if (!mFuture.valid()) {
+            mFuture = f();
+        }
+    }
+
+    void wait() {
+        std::lock_guard lock(mMutex);
+        if (mFuture.valid()) {
+            mFuture.wait();
+            mFuture = {};
+        }
+    }
+
+private:
+    std::mutex mMutex;
+    std::future<void> mFuture GUARDED_BY(mMutex);
+};
+
+} // namespace android::utils
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
index 5ef22b5..6b971a7 100644
--- a/services/surfaceflinger/common/Android.bp
+++ b/services/surfaceflinger/common/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_defaults {
@@ -18,7 +19,7 @@
         "server_configurable_flags",
     ],
     static_libs: [
-        "librenderengine",
+        "librenderengine_includes",
     ],
     srcs: [
         "FlagManager.cpp",
@@ -34,6 +35,8 @@
     ],
     static_libs: [
         "libsurfaceflingerflags",
+        "android.os.flags-aconfig-cc",
+        "android.server.display.flags-aconfig-cc",
     ],
 }
 
@@ -44,5 +47,33 @@
     ],
     static_libs: [
         "libsurfaceflingerflags_test",
+        "android.os.flags-aconfig-cc-test",
+        "android.server.display.flags-aconfig-cc",
+    ],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_common_deps",
+    shared_libs: [
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
+        "android.os.flags-aconfig-cc",
+        "android.server.display.flags-aconfig-cc",
+    ],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_common_test_deps",
+    shared_libs: [
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "libsurfaceflinger_common_test",
+        "libsurfaceflingerflags_test",
+        "android.os.flags-aconfig-cc-test",
+        "android.server.display.flags-aconfig-cc",
     ],
 }
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index cb1faee..57b170f 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -26,7 +26,9 @@
 #include <server_configurable_flags/get_flags.h>
 #include <cinttypes>
 
+#include <android_os.h>
 #include <com_android_graphics_surfaceflinger_flags.h>
+#include <com_android_server_display_feature_flags.h>
 
 namespace android {
 using namespace com::android::graphics::surfaceflinger;
@@ -108,13 +110,14 @@
     DUMP_SERVER_FLAG(use_skia_tracing);
 
     /// Trunk stable server flags ///
-    DUMP_SERVER_FLAG(late_boot_misc2);
-    DUMP_SERVER_FLAG(dont_skip_on_early);
     DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
+    DUMP_SERVER_FLAG(adpf_gpu_sf);
+    DUMP_SERVER_FLAG(adpf_use_fmq_channel);
 
     /// Trunk stable readonly flags ///
     DUMP_READ_ONLY_FLAG(connected_display);
     DUMP_READ_ONLY_FLAG(enable_small_area_detection);
+    DUMP_READ_ONLY_FLAG(frame_rate_category_mrr);
     DUMP_READ_ONLY_FLAG(misc1);
     DUMP_READ_ONLY_FLAG(vrr_config);
     DUMP_READ_ONLY_FLAG(hotplug2);
@@ -122,12 +125,30 @@
     DUMP_READ_ONLY_FLAG(multithreaded_present);
     DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace);
     DUMP_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency);
-    DUMP_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved);
+    DUMP_READ_ONLY_FLAG(cache_when_source_crop_layer_only_moved);
     DUMP_READ_ONLY_FLAG(enable_fro_dependent_features);
     DUMP_READ_ONLY_FLAG(display_protected);
     DUMP_READ_ONLY_FLAG(fp16_client_target);
     DUMP_READ_ONLY_FLAG(game_default_frame_rate);
     DUMP_READ_ONLY_FLAG(enable_layer_command_batching);
+    DUMP_READ_ONLY_FLAG(screenshot_fence_preservation);
+    DUMP_READ_ONLY_FLAG(vulkan_renderengine);
+    DUMP_READ_ONLY_FLAG(renderable_buffer_usage);
+    DUMP_READ_ONLY_FLAG(vrr_bugfix_24q4);
+    DUMP_READ_ONLY_FLAG(restore_blur_step);
+    DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro);
+    DUMP_READ_ONLY_FLAG(protected_if_client);
+    DUMP_READ_ONLY_FLAG(ce_fence_promise);
+    DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout);
+    DUMP_READ_ONLY_FLAG(graphite_renderengine);
+    DUMP_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed);
+    DUMP_READ_ONLY_FLAG(deprecate_vsync_sf);
+    DUMP_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter);
+    DUMP_READ_ONLY_FLAG(detached_mirror);
+    DUMP_READ_ONLY_FLAG(commit_not_composited);
+    DUMP_READ_ONLY_FLAG(local_tonemap_screenshots);
+    DUMP_READ_ONLY_FLAG(override_trusted_overlay);
+
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
 #undef DUMP_FLAG_INTERVAL
@@ -154,7 +175,7 @@
         return getServerConfigurableFlag(serverFlagName);                                   \
     }
 
-#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted)                \
+#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted, owner)         \
     bool FlagManager::name() const {                                                            \
         if (checkForBootCompleted) {                                                            \
             LOG_ALWAYS_FATAL_IF(!mBootCompleted,                                                \
@@ -162,21 +183,27 @@
                                 __func__);                                                      \
         }                                                                                       \
         static const std::optional<bool> debugOverride = getBoolProperty(syspropOverride);      \
-        static const bool value = getFlagValue([] { return flags::name(); }, debugOverride);    \
+        static const bool value = getFlagValue([] { return owner ::name(); }, debugOverride);   \
         if (mUnitTestMode) {                                                                    \
             /*                                                                                  \
              * When testing, we don't want to rely on the cached `value` or the debugOverride.  \
              */                                                                                 \
-            return flags::name();                                                               \
+            return owner ::name();                                                              \
         }                                                                                       \
         return value;                                                                           \
     }
 
 #define FLAG_MANAGER_SERVER_FLAG(name, syspropOverride) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true)
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, flags)
 
 #define FLAG_MANAGER_READ_ONLY_FLAG(name, syspropOverride) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false)
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, flags)
+
+#define FLAG_MANAGER_SERVER_FLAG_IMPORTED(name, syspropOverride, owner) \
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, owner)
+
+#define FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(name, syspropOverride, owner) \
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, owner)
 
 /// Legacy server flags ///
 FLAG_MANAGER_LEGACY_SERVER_FLAG(test_flag, "", "")
@@ -188,6 +215,7 @@
 /// Trunk stable readonly flags ///
 FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
 FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
+FLAG_MANAGER_READ_ONLY_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr")
 FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
 FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
@@ -195,23 +223,40 @@
 FLAG_MANAGER_READ_ONLY_FLAG(multithreaded_present, "debug.sf.multithreaded_present")
 FLAG_MANAGER_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace, "")
 FLAG_MANAGER_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency, "")
-FLAG_MANAGER_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved,
+FLAG_MANAGER_READ_ONLY_FLAG(cache_when_source_crop_layer_only_moved,
                             "debug.sf.cache_source_crop_only_moved")
 FLAG_MANAGER_READ_ONLY_FLAG(enable_fro_dependent_features, "")
 FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
 FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
 FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
-FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "debug.sf.enable_layer_command_batching")
+FLAG_MANAGER_READ_ONLY_FLAG(screenshot_fence_preservation, "debug.sf.screenshot_fence_preservation")
+FLAG_MANAGER_READ_ONLY_FLAG(vulkan_renderengine, "debug.renderengine.vulkan")
+FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "")
+FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step")
+FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "")
+FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "")
+FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_24q4, "");
+FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
+FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite")
+FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
+FLAG_MANAGER_READ_ONLY_FLAG(deprecate_vsync_sf, "");
+FLAG_MANAGER_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter, "");
+FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, "");
+FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, "");
+FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots");
+FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, "");
 
 /// Trunk stable server flags ///
-FLAG_MANAGER_SERVER_FLAG(late_boot_misc2, "")
 FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
+FLAG_MANAGER_SERVER_FLAG(adpf_gpu_sf, "")
 
-/// Exceptions ///
-bool FlagManager::dont_skip_on_early() const {
-    // Even though this is a server writable flag, we do call it before boot completed, but that's
-    // fine since the decision is done per frame. We can't do caching though.
-    return flags::dont_skip_on_early();
-}
+/// Trunk stable server flags from outside SurfaceFlinger ///
+FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os)
+
+/// Trunk stable readonly flags from outside SurfaceFlinger ///
+FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(idle_screen_refresh_rate_timeout, "",
+                                     com::android::server::display::feature::flags)
+FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(adpf_use_fmq_channel_fixed, "", android::os)
 
 } // namespace android
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 8b5983d..9517ff7 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -48,12 +48,14 @@
     bool use_skia_tracing() const;
 
     /// Trunk stable server flags ///
-    bool late_boot_misc2() const;
-    bool dont_skip_on_early() const;
     bool refresh_rate_overlay_on_external_display() const;
+    bool adpf_gpu_sf() const;
+    bool adpf_use_fmq_channel() const;
+    bool adpf_use_fmq_channel_fixed() const;
 
     /// Trunk stable readonly flags ///
     bool connected_display() const;
+    bool frame_rate_category_mrr() const;
     bool enable_small_area_detection() const;
     bool misc1() const;
     bool vrr_config() const;
@@ -62,12 +64,29 @@
     bool multithreaded_present() const;
     bool add_sf_skipped_frames_to_trace() const;
     bool use_known_refresh_rate_for_fps_consistency() const;
-    bool cache_if_source_crop_layer_only_moved() const;
+    bool cache_when_source_crop_layer_only_moved() const;
     bool enable_fro_dependent_features() const;
     bool display_protected() const;
     bool fp16_client_target() const;
     bool game_default_frame_rate() const;
     bool enable_layer_command_batching() const;
+    bool screenshot_fence_preservation() const;
+    bool vulkan_renderengine() const;
+    bool vrr_bugfix_24q4() const;
+    bool renderable_buffer_usage() const;
+    bool restore_blur_step() const;
+    bool dont_skip_on_early_ro() const;
+    bool protected_if_client() const;
+    bool ce_fence_promise() const;
+    bool idle_screen_refresh_rate_timeout() const;
+    bool graphite_renderengine() const;
+    bool latch_unsignaled_with_auto_refresh_changed() const;
+    bool deprecate_vsync_sf() const;
+    bool allow_n_vsyncs_in_targeter() const;
+    bool detached_mirror() const;
+    bool commit_not_composited() const;
+    bool local_tonemap_screenshots() const;
+    bool override_trusted_overlay() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/common/include/common/test/FlagUtils.h b/services/surfaceflinger/common/include/common/test/FlagUtils.h
index 550c70d..5317cbb 100644
--- a/services/surfaceflinger/common/include/common/test/FlagUtils.h
+++ b/services/surfaceflinger/common/include/common/test/FlagUtils.h
@@ -18,7 +18,14 @@
 
 #include <common/FlagManager.h>
 
-#define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value))
+// indirection to resolve __LINE__ in SET_FLAG_FOR_TEST, it's used to create a unique TestFlagSetter
+// setter var name everytime so multiple flags can be set in a test
+#define CONCAT_INNER(a, b) a##b
+#define CONCAT(a, b) CONCAT_INNER(a, b)
+#define SET_FLAG_FOR_TEST(name, value)            \
+    TestFlagSetter CONCAT(_testFlag_, __LINE__) { \
+        (name), (name), (value)                   \
+    }
 
 namespace android {
 class TestFlagSetter {
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index ab3b352..ae502cf 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -22,50 +22,23 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_defaults {
     name: "surfaceflinger_fuzz_defaults",
-    include_dirs: [
-        "frameworks/native/services/surfaceflinger/tests/unittests",
-    ],
     static_libs: [
-        "android.hardware.graphics.composer@2.1-resources",
         "libc++fs",
-        "libgmock",
-        "libgui_mocks",
-        "libgmock_ndk",
-        "libgmock_main",
-        "libgtest_ndk_c++",
-        "libgmock_main_ndk",
-        "librenderengine_mocks",
         "libsurfaceflinger_common",
-        "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",
@@ -90,66 +63,6 @@
 }
 
 cc_fuzz {
-    name: "surfaceflinger_fuzzer",
-    defaults: [
-        "surfaceflinger_fuzz_defaults",
-    ],
-    srcs: [
-        "surfaceflinger_fuzzer.cpp",
-    ],
-}
-
-cc_fuzz {
-    name: "surfaceflinger_displayhardware_fuzzer",
-    defaults: [
-        "surfaceflinger_fuzz_defaults",
-    ],
-    srcs: [
-        "surfaceflinger_displayhardware_fuzzer.cpp",
-    ],
-    header_libs: [
-        "android.hardware.graphics.composer@2.4-command-buffer",
-        "android.hardware.graphics.composer@2.4-hal",
-    ],
-}
-
-cc_fuzz {
-    name: "surfaceflinger_scheduler_fuzzer",
-    defaults: [
-        "surfaceflinger_fuzz_defaults",
-    ],
-    srcs: [
-        "surfaceflinger_scheduler_fuzzer.cpp",
-    ],
-}
-
-cc_fuzz {
-    name: "surfaceflinger_layer_fuzzer",
-    defaults: [
-        "surfaceflinger_fuzz_defaults",
-    ],
-    header_libs: [
-        "libgui_headers",
-    ],
-    static_libs: [
-        "librenderengine",
-    ],
-    srcs: [
-        "surfaceflinger_layer_fuzzer.cpp",
-    ],
-}
-
-cc_fuzz {
-    name: "surfaceflinger_frametracer_fuzzer",
-    defaults: [
-        "surfaceflinger_fuzz_defaults",
-    ],
-    srcs: [
-        "surfaceflinger_frametracer_fuzzer.cpp",
-    ],
-}
-
-cc_fuzz {
     name: "surfaceflinger_service_fuzzer",
     defaults: [
         "surfaceflinger_fuzz_defaults",
diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md
deleted file mode 100644
index a06c41b..0000000
--- a/services/surfaceflinger/fuzzer/README.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# Fuzzers for SurfaceFlinger
-## Table of contents
-+ [SurfaceFlinger](#SurfaceFlinger)
-+ [DisplayHardware](#DisplayHardware)
-+ [Scheduler](#Scheduler)
-+ [Layer](#Layer)
-+ [FrameTracer](#FrameTracer)
-
-# <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
-```
-
-# <a name="DisplayHardware"></a> Fuzzer for DisplayHardware
-
-DisplayHardware supports the following parameters:
-1. Hal Capability (parameter name: `hasCapability`)
-2. Hal BlendMode (parameter name: `setBlendMode`)
-3. Hal Composition (parameter name: `setCompositionType`)
-4. Hal Display Capability (parameter name: `hasDisplayCapability`)
-5. Composition Types (parameter name: `prepareFrame`)
-6. Color Modes (parameter name: `setActiveColorMode`)
-7. Render Intents (parameter name: `setActiveColorMode`)
-8. Power Modes (parameter name: `setPowerMode`)
-9. Content Types (parameter name: `setContentType`)
-10. Data Space (parameter name: `setDataspace`)
-11. Transforms (parameter name: `setLayerTransform`)
-
-You can find the possible values in the fuzzer's source code.
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) surfaceflinger_displayhardware_fuzzer
-```
-2. Run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/surfaceflinger_displayhardware_fuzzer/surfaceflinger_displayhardware_fuzzer
-```
-
-# <a name="Scheduler"></a> Fuzzer for Scheduler
-
-Scheduler supports the following parameters:
-1. VSync Periods (parameter name: `lowFpsPeriod`)
-
-You can find the possible values in the fuzzer's source code.
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) surfaceflinger_scheduler_fuzzer
-```
-2. To run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/surfaceflinger_scheduler_fuzzer/surfaceflinger_scheduler_fuzzer
-```
-
-# <a name="Layer"></a> Fuzzer for Layer
-
-Layer supports the following parameters:
-1. Display Connection Types (parameter name: `fakeDisplay`)
-2. State Sets (parameter name: `traverseInZOrder`)
-3. Disconnect modes (parameter name: `disconnect`)
-4. Data Spaces (parameter name: `setDataspace`)
-
-You can find the possible values in the fuzzer's source code.
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) surfaceflinger_layer_fuzzer
-```
-2. Run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/surfaceflinger_layer_fuzzer/surfaceflinger_layer_fuzzer
-```
-
-# <a name="FrameTracer"></a> Fuzzer for FrameTracer
-
-#### Steps to run
-1. Build the fuzzer
-```
-  $ mm -j$(nproc) surfaceflinger_frametracer_fuzzer
-```
-2. To run on device
-```
-  $ adb sync data
-  $ adb shell /data/fuzz/arm64/surfaceflinger_frametracer_fuzzer/surfaceflinger_frametracer_fuzzer
-```
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
deleted file mode 100644
index 68237c8..0000000
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ /dev/null
@@ -1,670 +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.
- *
- */
-
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
-#include <compositionengine/impl/OutputCompositionState.h>
-#include <fuzzer/FuzzedDataProvider.h>
-#include <gui/BLASTBufferQueue.h>
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/IProducerListener.h>
-#include <gui/LayerDebugInfo.h>
-#include <gui/SurfaceComposerClient.h>
-#include <hidl/ServiceManagement.h>
-#include <hwbinder/ProcessState.h>
-#include <ui/DisplayIdentification.h>
-
-#include "DisplayHardware/AidlComposerHal.h"
-#include "DisplayHardware/DisplayMode.h"
-#include "DisplayHardware/FramebufferSurface.h"
-#include "DisplayHardware/HWComposer.h"
-#include "DisplayHardware/PowerAdvisor.h"
-#include "DisplayHardware/VirtualDisplaySurface.h"
-#include "SurfaceFlinger.h"
-#include "surfaceflinger_displayhardware_fuzzer_utils.h"
-
-#include <FuzzableDataspaces.h>
-
-namespace android::fuzz {
-
-using namespace android::hardware::graphics::common;
-using namespace android::hardware::graphics::composer;
-namespace aidl = aidl::android::hardware::graphics::composer3;
-namespace hal = android::hardware::graphics::composer::hal;
-using Config = hal::V2_1::Config;
-using Display = hal::V2_1::Display;
-using RenderIntent = V1_1::RenderIntent;
-using IComposerClient = hal::V2_4::IComposerClient;
-using VsyncPeriodChangeTimeline = hal::V2_4::VsyncPeriodChangeTimeline;
-using PerFrameMetadata = IComposerClient::PerFrameMetadata;
-using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
-using Vsync = IComposerClient::Vsync;
-
-static constexpr hal::Transform kTransforms[] = {hal::Transform::FLIP_H, hal::Transform::FLIP_V,
-                                                 hal::Transform::ROT_90, hal::Transform::ROT_180,
-                                                 hal::Transform::ROT_270};
-
-static constexpr aidl::Capability kCapability[] = {aidl::Capability::INVALID,
-                                                   aidl::Capability::SIDEBAND_STREAM,
-                                                   aidl::Capability::SKIP_CLIENT_COLOR_TRANSFORM,
-                                                   aidl::Capability::PRESENT_FENCE_IS_NOT_RELIABLE,
-                                                   aidl::Capability::SKIP_VALIDATE};
-
-static constexpr hal::BlendMode kBlendModes[] = {hal::BlendMode::INVALID, hal::BlendMode::NONE,
-                                                 hal::BlendMode::PREMULTIPLIED,
-                                                 hal::BlendMode::COVERAGE};
-
-static constexpr Composition kCompositions[] = {Composition::INVALID, Composition::CLIENT,
-                                                Composition::DEVICE,  Composition::SOLID_COLOR,
-                                                Composition::CURSOR,  Composition::SIDEBAND};
-
-static constexpr DisplayCapability kDisplayCapability[] =
-        {DisplayCapability::INVALID,
-         DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM,
-         DisplayCapability::DOZE,
-         DisplayCapability::BRIGHTNESS,
-         DisplayCapability::PROTECTED_CONTENTS,
-         DisplayCapability::AUTO_LOW_LATENCY_MODE};
-
-static constexpr VirtualDisplaySurface::CompositionType kCompositionTypes[] =
-        {VirtualDisplaySurface::CompositionType::Unknown,
-         VirtualDisplaySurface::CompositionType::Gpu, VirtualDisplaySurface::CompositionType::Hwc,
-         VirtualDisplaySurface::CompositionType::Mixed};
-
-static constexpr ui::RenderIntent kRenderIntents[] = {ui::RenderIntent::COLORIMETRIC,
-                                                      ui::RenderIntent::ENHANCE,
-                                                      ui::RenderIntent::TONE_MAP_COLORIMETRIC,
-                                                      ui::RenderIntent::TONE_MAP_ENHANCE};
-
-static constexpr hal::PowerMode kPowerModes[] = {hal::PowerMode::OFF, hal::PowerMode::DOZE,
-                                                 hal::PowerMode::DOZE_SUSPEND, hal::PowerMode::ON,
-                                                 hal::PowerMode::ON_SUSPEND};
-
-static constexpr hal::ContentType kContentTypes[] = {hal::ContentType::NONE,
-                                                     hal::ContentType::GRAPHICS,
-                                                     hal::ContentType::PHOTO,
-                                                     hal::ContentType::CINEMA,
-                                                     hal::ContentType::GAME};
-
-const 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 constexpr hal::HWConfigId kActiveConfig = 0;
-
-class DisplayHardwareFuzzer {
-public:
-    DisplayHardwareFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) {
-        mPhysicalDisplayId = TestableSurfaceFlinger::getFirstDisplayId().value_or(
-                PhysicalDisplayId::fromPort(mFdp.ConsumeIntegral<uint8_t>()));
-    };
-    void process();
-
-private:
-    void invokeComposer();
-    void invokeDisplayIdentification();
-    void invokeLayer(HWC2::Layer* layer);
-    void setSidebandStream(HWC2::Layer* layer);
-    void setCursorPosition(HWC2::Layer* layer);
-    void setBuffer(HWC2::Layer* layer);
-    void setSurfaceDamage(HWC2::Layer* layer);
-    void setDisplayFrame(HWC2::Layer* layer);
-    void setVisibleRegion(HWC2::Layer* layer);
-    void setLayerGenericMetadata(HWC2::Layer* layer);
-    void invokeFrameBufferSurface();
-    void invokeVirtualDisplaySurface();
-    void invokeAidlComposer();
-    Display createVirtualDisplay(Hwc2::AidlComposer*);
-    void validateDisplay(Hwc2::AidlComposer*, Display);
-    void presentOrValidateDisplay(Hwc2::AidlComposer*, Display);
-    void setOutputBuffer(Hwc2::AidlComposer*, Display);
-    void setLayerSidebandStream(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
-    void invokeComposerHal2_2(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
-    void invokeComposerHal2_3(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
-    void invokeComposerHal2_4(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
-    void getDisplayVsyncPeriod();
-    void setActiveModeWithConstraints();
-    void getDisplayIdentificationData();
-    void dumpHwc();
-    void getDisplayedContentSamplingAttributes(HalDisplayId);
-    void getDeviceCompositionChanges(HalDisplayId);
-    void getHdrCapabilities(HalDisplayId);
-    void getDisplayedContentSample(HalDisplayId);
-    void getSupportedContentTypes();
-    ui::Size getFuzzedSize();
-    mat4 getFuzzedMatrix();
-
-    DisplayIdGenerator<HalVirtualDisplayId> mGenerator;
-    FuzzedDataProvider mFdp;
-    PhysicalDisplayId mPhysicalDisplayId;
-    android::impl::HWComposer mHwc{std::make_unique<Hwc2::mock::Composer>()};
-};
-
-void DisplayHardwareFuzzer::validateDisplay(Hwc2::AidlComposer* composer, Display display) {
-    uint32_t outNumTypes, outNumRequests;
-    const auto frameIntervalRange =
-            mFdp.ConsumeIntegralInRange<int32_t>(Fps::fromValue(1).getPeriodNsecs(),
-                                                 Fps::fromValue(120).getPeriodNsecs());
-    composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), frameIntervalRange,
-                              &outNumTypes, &outNumRequests);
-}
-
-void DisplayHardwareFuzzer::presentOrValidateDisplay(Hwc2::AidlComposer* composer,
-                                                     Display display) {
-    int32_t outPresentFence;
-    uint32_t outNumTypes, outNumRequests, state;
-    const auto frameIntervalRange =
-            mFdp.ConsumeIntegralInRange<int32_t>(Fps::fromValue(1).getPeriodNsecs(),
-                                                 Fps::fromValue(120).getPeriodNsecs());
-    composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), frameIntervalRange,
-                                       &outNumTypes, &outNumRequests, &outPresentFence, &state);
-}
-
-void DisplayHardwareFuzzer::setOutputBuffer(Hwc2::AidlComposer* composer, Display display) {
-    const native_handle_t buffer{};
-    composer->setOutputBuffer(display, &buffer, mFdp.ConsumeIntegral<int32_t>() /*releaseFence*/);
-}
-
-void DisplayHardwareFuzzer::setLayerSidebandStream(Hwc2::AidlComposer* composer, Display display,
-                                                   Hwc2::V2_4::hal::Layer outLayer) {
-    const native_handle_t stream{};
-    composer->setLayerSidebandStream(display, outLayer, &stream);
-}
-
-Display DisplayHardwareFuzzer::createVirtualDisplay(Hwc2::AidlComposer* composer) {
-    namespace types = hardware::graphics::common;
-    using types::V1_2::PixelFormat;
-    PixelFormat format{};
-    Display display;
-    composer->createVirtualDisplay(mFdp.ConsumeIntegral<uint32_t>() /*width*/,
-                                   mFdp.ConsumeIntegral<uint32_t>() /*height*/, &format, &display);
-    return display;
-}
-
-void DisplayHardwareFuzzer::getDisplayVsyncPeriod() {
-    nsecs_t outVsyncPeriod;
-    mHwc.getDisplayVsyncPeriod(mPhysicalDisplayId, &outVsyncPeriod);
-}
-
-void DisplayHardwareFuzzer::setActiveModeWithConstraints() {
-    hal::VsyncPeriodChangeTimeline outTimeline;
-    mHwc.setActiveModeWithConstraints(mPhysicalDisplayId, kActiveConfig, {} /*constraints*/,
-                                      &outTimeline);
-}
-
-void DisplayHardwareFuzzer::getDisplayIdentificationData() {
-    uint8_t outPort;
-    DisplayIdentificationData outData;
-    mHwc.getDisplayIdentificationData(kHwDisplayId, &outPort, &outData);
-}
-
-void DisplayHardwareFuzzer::dumpHwc() {
-    std::string string = mFdp.ConsumeRandomLengthString().c_str();
-    mHwc.dump(string);
-}
-
-void DisplayHardwareFuzzer::getDeviceCompositionChanges(HalDisplayId halDisplayID) {
-    std::optional<impl::HWComposer::DeviceRequestedChanges> outChanges;
-    mHwc.getDeviceCompositionChanges(halDisplayID,
-                                     mFdp.ConsumeBool() /*frameUsesClientComposition*/,
-                                     std::chrono::steady_clock::now(),
-                                     mFdp.ConsumeIntegral<nsecs_t>(),
-                                     Fps::fromValue(
-                                             mFdp.ConsumeFloatingPointInRange<float>(1.f, 120.f)),
-                                     &outChanges);
-}
-
-void DisplayHardwareFuzzer::getDisplayedContentSamplingAttributes(HalDisplayId halDisplayID) {
-    uint8_t outComponentMask;
-    ui::Dataspace dataSpace;
-    ui::PixelFormat pixelFormat;
-    mHwc.getDisplayedContentSamplingAttributes(halDisplayID, &pixelFormat, &dataSpace,
-                                               &outComponentMask);
-}
-
-void DisplayHardwareFuzzer::getHdrCapabilities(HalDisplayId halDisplayID) {
-    HdrCapabilities outCapabilities;
-    mHwc.getHdrCapabilities(halDisplayID, &outCapabilities);
-}
-
-void DisplayHardwareFuzzer::getDisplayedContentSample(HalDisplayId halDisplayID) {
-    DisplayedFrameStats outStats;
-    mHwc.getDisplayedContentSample(halDisplayID, mFdp.ConsumeIntegral<uint64_t>() /* maxFrames*/,
-                                   mFdp.ConsumeIntegral<uint64_t>() /*timestamps*/, &outStats);
-}
-
-void DisplayHardwareFuzzer::getSupportedContentTypes() {
-    std::vector<hal::ContentType> contentType{};
-    mHwc.getSupportedContentTypes(mPhysicalDisplayId, &contentType);
-}
-
-void DisplayHardwareFuzzer::invokeAidlComposer() {
-    hardware::ProcessState::self()->startThreadPool();
-    ProcessState::self()->startThreadPool();
-
-    if (!Hwc2::AidlComposer::isDeclared("default")) {
-        return;
-    }
-
-    Hwc2::AidlComposer composer("default");
-
-    android::hardware::graphics::composer::hal::TestHWC2ComposerCallback composerCallback{};
-    composer.registerCallback(composerCallback);
-
-    Display display = createVirtualDisplay(&composer);
-
-    composer.acceptDisplayChanges(display);
-
-    Hwc2::V2_4::hal::Layer outLayer;
-    composer.createLayer(display, &outLayer);
-
-    int32_t outPresentFence;
-    composer.presentDisplay(display, &outPresentFence);
-
-    composer.setActiveConfig(display, Config{});
-
-    composer.setClientTarget(display, mFdp.ConsumeIntegral<uint32_t>(), sp<GraphicBuffer>(),
-                             mFdp.ConsumeIntegral<int32_t>(), mFdp.PickValueInArray(kDataspaces),
-                             {}, mFdp.ConsumeFloatingPoint<float>());
-
-    composer.setColorMode(display, mFdp.PickValueInArray(kColormodes),
-                          mFdp.PickValueInArray(kRenderIntents));
-
-    setOutputBuffer(&composer, display);
-
-    composer.setPowerMode(display, mFdp.PickValueInArray(kPowerModes));
-    composer.setVsyncEnabled(display, mFdp.ConsumeBool() ? Vsync::ENABLE : Vsync::DISABLE);
-
-    composer.setClientTargetSlotCount(display);
-
-    validateDisplay(&composer, display);
-
-    presentOrValidateDisplay(&composer, display);
-
-    composer.setCursorPosition(display, outLayer, mFdp.ConsumeIntegral<uint8_t>() /*x*/,
-                               mFdp.ConsumeIntegral<uint8_t>() /*y*/);
-
-    composer.setLayerBuffer(display, outLayer, mFdp.ConsumeIntegral<uint32_t>() /*slot*/,
-                            sp<GraphicBuffer>(), mFdp.ConsumeIntegral<int32_t>() /*acquireFence*/);
-
-    composer.setLayerSurfaceDamage(display, outLayer, {} /*damage*/);
-
-    composer.setLayerBlendMode(display, outLayer, mFdp.PickValueInArray(kBlendModes));
-
-    composer.setLayerColor(display, outLayer,
-                           {mFdp.ConsumeFloatingPoint<float>() /*red*/,
-                            mFdp.ConsumeFloatingPoint<float>() /*green*/,
-                            mFdp.ConsumeFloatingPoint<float>() /*blue*/,
-                            mFdp.ConsumeFloatingPoint<float>() /*alpha*/});
-    composer.setLayerCompositionType(display, outLayer, mFdp.PickValueInArray(kCompositions));
-    composer.setLayerDataspace(display, outLayer, mFdp.PickValueInArray(kDataspaces));
-    composer.setLayerDisplayFrame(display, outLayer, {} /*frame*/);
-    composer.setLayerPlaneAlpha(display, outLayer, mFdp.ConsumeFloatingPoint<float>());
-
-    setLayerSidebandStream(&composer, display, outLayer);
-
-    composer.setLayerSourceCrop(display, outLayer, {} /*crop*/);
-
-    composer.setLayerTransform(display, outLayer, mFdp.PickValueInArray(kTransforms));
-
-    composer.setLayerVisibleRegion(display, outLayer, std::vector<IComposerClient::Rect>{});
-    composer.setLayerZOrder(display, outLayer, mFdp.ConsumeIntegral<uint32_t>());
-
-    invokeComposerHal2_2(&composer, display, outLayer);
-    invokeComposerHal2_3(&composer, display, outLayer);
-    invokeComposerHal2_4(&composer, display, outLayer);
-
-    composer.executeCommands(display);
-
-    composer.destroyLayer(display, outLayer);
-    composer.destroyVirtualDisplay(display);
-}
-
-void DisplayHardwareFuzzer::invokeComposerHal2_2(Hwc2::AidlComposer* composer, Display display,
-                                                 Hwc2::V2_4::hal::Layer outLayer) {
-    const std::vector<PerFrameMetadata> perFrameMetadatas;
-    composer->setLayerPerFrameMetadata(display, outLayer, perFrameMetadatas);
-
-    composer->getPerFrameMetadataKeys(display);
-    std::vector<RenderIntent> outRenderIntents;
-
-    composer->getRenderIntents(display, mFdp.PickValueInArray(kColormodes), &outRenderIntents);
-    mat4 outMatrix;
-    composer->getDataspaceSaturationMatrix(mFdp.PickValueInArray(kDataspaces), &outMatrix);
-}
-
-void DisplayHardwareFuzzer::invokeComposerHal2_3(Hwc2::AidlComposer* composer, Display display,
-                                                 Hwc2::V2_4::hal::Layer outLayer) {
-    composer->setDisplayContentSamplingEnabled(display, mFdp.ConsumeBool() /*enabled*/,
-                                               mFdp.ConsumeIntegral<uint8_t>() /*componentMask*/,
-                                               mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/);
-
-    DisplayedFrameStats outStats;
-    composer->getDisplayedContentSample(display, mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/,
-                                        mFdp.ConsumeIntegral<uint64_t>() /*timestamp*/, &outStats);
-
-    composer->setLayerPerFrameMetadataBlobs(display, outLayer, std::vector<PerFrameMetadataBlob>{});
-
-    composer->setDisplayBrightness(display, mFdp.ConsumeFloatingPoint<float>(),
-                                   mFdp.ConsumeFloatingPoint<float>(),
-                                   Hwc2::Composer::DisplayBrightnessOptions{
-                                           .applyImmediately = mFdp.ConsumeIntegral<bool>()});
-}
-
-void DisplayHardwareFuzzer::invokeComposerHal2_4(Hwc2::AidlComposer* composer, Display display,
-                                                 Hwc2::V2_4::hal::Layer outLayer) {
-    VsyncPeriodChangeTimeline outTimeline;
-    composer->setActiveConfigWithConstraints(display, Config{},
-                                             IComposerClient::VsyncPeriodChangeConstraints{},
-                                             &outTimeline);
-
-    composer->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
-
-    composer->setContentType(display, mFdp.PickValueInArray(kContentTypes));
-
-    std::vector<uint8_t> value;
-    value.push_back(mFdp.ConsumeIntegral<uint8_t>());
-    composer->setLayerGenericMetadata(display, outLayer, mFdp.ConsumeRandomLengthString() /*key*/,
-                                      mFdp.ConsumeBool() /*mandatory*/, value);
-}
-
-ui::Size DisplayHardwareFuzzer::getFuzzedSize() {
-    ui::Size size{mFdp.ConsumeIntegral<int32_t>() /*width*/,
-                  mFdp.ConsumeIntegral<int32_t>() /*height*/};
-    return size;
-}
-
-mat4 DisplayHardwareFuzzer::getFuzzedMatrix() {
-    mat4 matrix{mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
-                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>()};
-    return matrix;
-}
-
-void DisplayHardwareFuzzer::setCursorPosition(HWC2::Layer* layer) {
-    layer->setCursorPosition(mFdp.ConsumeIntegral<int32_t>() /*x*/,
-                             mFdp.ConsumeIntegral<int32_t>() /*y*/);
-}
-
-void DisplayHardwareFuzzer::setBuffer(HWC2::Layer* layer) {
-    layer->setBuffer(mFdp.ConsumeIntegral<uint32_t>() /*slot*/, sp<GraphicBuffer>(),
-                     sp<Fence>::make());
-}
-
-void DisplayHardwareFuzzer::setSurfaceDamage(HWC2::Layer* layer) {
-    Rect rhs{mFdp.ConsumeIntegral<uint32_t>() /*width*/,
-             mFdp.ConsumeIntegral<uint32_t>() /*height*/};
-    const Region damage{rhs};
-    layer->setSurfaceDamage(damage);
-}
-
-void DisplayHardwareFuzzer::setVisibleRegion(HWC2::Layer* layer) {
-    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
-    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
-    Rect rect{width, height};
-    const Region region{rect};
-    layer->setVisibleRegion(region);
-}
-
-void DisplayHardwareFuzzer::setDisplayFrame(HWC2::Layer* layer) {
-    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
-    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
-    const Rect frame{width, height};
-    layer->setDisplayFrame(frame);
-}
-
-void DisplayHardwareFuzzer::setLayerGenericMetadata(HWC2::Layer* layer) {
-    std::vector<uint8_t> value;
-    value.push_back(mFdp.ConsumeIntegral<uint8_t>());
-    layer->setLayerGenericMetadata(mFdp.ConsumeRandomLengthString().c_str() /*name*/,
-                                   mFdp.ConsumeBool() /*mandatory*/, value);
-}
-
-void DisplayHardwareFuzzer::setSidebandStream(HWC2::Layer* layer) {
-    const native_handle_t stream{};
-    layer->setSidebandStream(&stream);
-}
-
-void DisplayHardwareFuzzer::invokeLayer(HWC2::Layer* layer) {
-    setCursorPosition(layer);
-    setBuffer(layer);
-    setSurfaceDamage(layer);
-
-    layer->setBlendMode(mFdp.PickValueInArray(kBlendModes));
-    layer->setColor({mFdp.ConsumeFloatingPoint<float>() /*red*/,
-                     mFdp.ConsumeFloatingPoint<float>() /*green*/,
-                     mFdp.ConsumeFloatingPoint<float>() /*blue*/,
-                     mFdp.ConsumeFloatingPoint<float>() /*alpha*/});
-    layer->setCompositionType(mFdp.PickValueInArray(kCompositions));
-    layer->setDataspace(mFdp.PickValueInArray(kDataspaces));
-
-    layer->setPerFrameMetadata(mFdp.ConsumeIntegral<int32_t>(), getFuzzedHdrMetadata(&mFdp));
-    setDisplayFrame(layer);
-
-    layer->setPlaneAlpha(mFdp.ConsumeFloatingPoint<float>());
-
-    setSidebandStream(layer);
-
-    layer->setSourceCrop(getFuzzedFloatRect(&mFdp));
-    layer->setTransform(mFdp.PickValueInArray(kTransforms));
-
-    setVisibleRegion(layer);
-
-    layer->setZOrder(mFdp.ConsumeIntegral<uint32_t>());
-
-    layer->setColorTransform(getFuzzedMatrix());
-
-    setLayerGenericMetadata(layer);
-}
-
-void DisplayHardwareFuzzer::invokeFrameBufferSurface() {
-    sp<IGraphicBufferProducer> bqProducer = sp<mock::GraphicBufferProducer>::make();
-    sp<IGraphicBufferConsumer> bqConsumer;
-    BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
-
-    sp<FramebufferSurface> surface =
-            sp<FramebufferSurface>::make(mHwc, mPhysicalDisplayId, bqConsumer,
-                                         getFuzzedSize() /*size*/, getFuzzedSize() /*maxSize*/);
-    surface->beginFrame(mFdp.ConsumeBool());
-
-    surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
-    surface->advanceFrame(mFdp.ConsumeFloatingPoint<float>());
-    surface->onFrameCommitted();
-    String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
-    surface->dumpAsString(result);
-    surface->resizeBuffers(getFuzzedSize());
-    surface->getClientTargetAcquireFence();
-}
-
-void DisplayHardwareFuzzer::invokeVirtualDisplaySurface() {
-    DisplayIdGenerator<HalVirtualDisplayId> mGenerator;
-    VirtualDisplayId VirtualDisplayId = mGenerator.generateId().value();
-
-    sp<SurfaceComposerClient> mClient = sp<SurfaceComposerClient>::make();
-    sp<SurfaceControl> mSurfaceControl =
-            mClient->createSurface(String8("TestSurface"), 100, 100, PIXEL_FORMAT_RGBA_8888,
-                                   ISurfaceComposerClient::eFXSurfaceBufferState,
-                                   /*parent*/ nullptr);
-
-    auto mBlastBufferQueueAdapter =
-            sp<BLASTBufferQueue>::make("TestBLASTBufferQueue", mSurfaceControl, 100, 100,
-                                       PIXEL_FORMAT_RGBA_8888);
-
-    sp<IGraphicBufferProducer> sink = mBlastBufferQueueAdapter->getIGraphicBufferProducer();
-    sp<IGraphicBufferProducer> bqProducer = mBlastBufferQueueAdapter->getIGraphicBufferProducer();
-    sp<IGraphicBufferConsumer> bqConsumer;
-    BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
-    BufferQueue::createBufferQueue(&sink, &bqConsumer);
-
-    auto surface =
-            sp<VirtualDisplaySurface>::make(mHwc, VirtualDisplayId, sink, bqProducer, bqConsumer,
-                                            mFdp.ConsumeRandomLengthString().c_str() /*name*/);
-
-    surface->beginFrame(mFdp.ConsumeBool());
-    surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
-    surface->resizeBuffers(getFuzzedSize());
-    surface->getClientTargetAcquireFence();
-    surface->advanceFrame(mFdp.ConsumeFloatingPoint<float>());
-    surface->onFrameCommitted();
-    String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
-    surface->dumpAsString(result);
-}
-
-void DisplayHardwareFuzzer::invokeComposer() {
-    HalVirtualDisplayId halVirtualDisplayId = mGenerator.generateId().value();
-    HalDisplayId halDisplayID = HalDisplayId{halVirtualDisplayId};
-
-    android::hardware::graphics::composer::hal::TestHWC2ComposerCallback composerCallback{};
-    mHwc.setCallback(composerCallback);
-
-    ui::PixelFormat pixelFormat{};
-    if (!mHwc.allocateVirtualDisplay(halVirtualDisplayId, getFuzzedSize(), &pixelFormat)) {
-        return;
-    }
-
-    getDisplayIdentificationData();
-
-    mHwc.hasDisplayCapability(halDisplayID, mFdp.PickValueInArray(kDisplayCapability));
-
-    mHwc.allocatePhysicalDisplay(kHwDisplayId, mPhysicalDisplayId);
-
-    static auto hwcLayer = mHwc.createLayer(halDisplayID);
-    HWC2::Layer* layer = hwcLayer.get();
-    invokeLayer(layer);
-
-    getDeviceCompositionChanges(halDisplayID);
-
-    mHwc.setClientTarget(halDisplayID, mFdp.ConsumeIntegral<uint32_t>(), Fence::NO_FENCE,
-                         sp<GraphicBuffer>::make(), mFdp.PickValueInArray(kDataspaces),
-                         mFdp.ConsumeFloatingPoint<float>());
-
-    mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now());
-
-    mHwc.setPowerMode(mPhysicalDisplayId, mFdp.PickValueInArray(kPowerModes));
-
-    mHwc.setColorTransform(halDisplayID, getFuzzedMatrix());
-
-    mHwc.getPresentFence(halDisplayID);
-
-    mHwc.getLayerReleaseFence(halDisplayID, layer);
-
-    mHwc.setOutputBuffer(halVirtualDisplayId, sp<Fence>::make(), sp<GraphicBuffer>::make());
-
-    mHwc.clearReleaseFences(halDisplayID);
-
-    getHdrCapabilities(halDisplayID);
-
-    mHwc.getSupportedPerFrameMetadata(halDisplayID);
-
-    mHwc.getRenderIntents(halDisplayID, ui::ColorMode());
-
-    mHwc.getDataspaceSaturationMatrix(halDisplayID, ui::Dataspace());
-
-    getDisplayedContentSamplingAttributes(halDisplayID);
-
-    mHwc.setDisplayContentSamplingEnabled(halDisplayID, mFdp.ConsumeBool() /*enabled*/,
-                                          mFdp.ConsumeIntegral<uint8_t>() /*componentMask*/,
-                                          mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/);
-
-    getDisplayedContentSample(halDisplayID);
-
-    mHwc.setDisplayBrightness(mPhysicalDisplayId, mFdp.ConsumeFloatingPoint<float>(),
-                              mFdp.ConsumeFloatingPoint<float>(),
-                              Hwc2::Composer::DisplayBrightnessOptions{
-                                      .applyImmediately = mFdp.ConsumeIntegral<bool>()});
-
-    mHwc.onHotplug(kHwDisplayId, hal::Connection::CONNECTED);
-    mHwc.updatesDeviceProductInfoOnHotplugReconnect();
-
-    mHwc.onVsync(kHwDisplayId, mFdp.ConsumeIntegral<int64_t>());
-    mHwc.setVsyncEnabled(mPhysicalDisplayId,
-                         mFdp.ConsumeBool() ? hal::Vsync::ENABLE : hal::Vsync::DISABLE);
-
-    mHwc.isConnected(mPhysicalDisplayId);
-    mHwc.getModes(mPhysicalDisplayId, mFdp.ConsumeIntegral<int32_t>());
-    mHwc.getActiveMode(mPhysicalDisplayId);
-    mHwc.getColorModes(mPhysicalDisplayId);
-    mHwc.hasCapability(mFdp.PickValueInArray(kCapability));
-
-    mHwc.setActiveColorMode(mPhysicalDisplayId, mFdp.PickValueInArray(kColormodes),
-                            mFdp.PickValueInArray(kRenderIntents));
-
-    mHwc.getDisplayConnectionType(mPhysicalDisplayId);
-    mHwc.isVsyncPeriodSwitchSupported(mPhysicalDisplayId);
-
-    getDisplayVsyncPeriod();
-
-    setActiveModeWithConstraints();
-
-    mHwc.setAutoLowLatencyMode(mPhysicalDisplayId, mFdp.ConsumeBool());
-
-    getSupportedContentTypes();
-
-    mHwc.setContentType(mPhysicalDisplayId, mFdp.PickValueInArray(kContentTypes));
-
-    dumpHwc();
-
-    mHwc.toPhysicalDisplayId(kHwDisplayId);
-    mHwc.fromPhysicalDisplayId(mPhysicalDisplayId);
-    mHwc.disconnectDisplay(halDisplayID);
-
-    static hal::HWDisplayId displayId = mFdp.ConsumeIntegral<hal::HWDisplayId>();
-    mHwc.onHotplug(displayId,
-                   mFdp.ConsumeBool() ? hal::Connection::DISCONNECTED : hal::Connection::CONNECTED);
-}
-
-template <size_t N>
-DisplayIdentificationData asDisplayIdentificationData(const unsigned char (&bytes)[N]) {
-    return DisplayIdentificationData(bytes, bytes + N - 1);
-}
-
-void DisplayHardwareFuzzer::invokeDisplayIdentification() {
-    static const DisplayIdentificationData data = asDisplayIdentificationData(kInternalEdid);
-    isEdid(data);
-    parseEdid(data);
-    parseDisplayIdentificationData(mFdp.ConsumeIntegral<uint8_t>(), data);
-    getPnpId(getVirtualDisplayId(mFdp.ConsumeIntegral<uint32_t>()));
-    getPnpId(mFdp.ConsumeIntegral<uint8_t>());
-}
-
-void DisplayHardwareFuzzer::process() {
-    invokeComposer();
-    invokeAidlComposer();
-    invokeDisplayIdentification();
-    invokeFrameBufferSurface();
-    invokeVirtualDisplaySurface();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    DisplayHardwareFuzzer displayHardwareFuzzer(data, size);
-    displayHardwareFuzzer.process();
-    return 0;
-}
-
-} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
deleted file mode 100644
index b2dc20e..0000000
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
+++ /dev/null
@@ -1,109 +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 <utils/Condition.h>
-#include <chrono>
-#include <vector>
-
-#include <android/hardware/graphics/composer/2.4/IComposer.h>
-#include <composer-hal/2.1/ComposerClient.h>
-#include <composer-hal/2.2/ComposerClient.h>
-#include <composer-hal/2.3/ComposerClient.h>
-#include <composer-hal/2.4/ComposerClient.h>
-
-#include "DisplayHardware/HWC2.h"
-#include "surfaceflinger_fuzzers_utils.h"
-
-namespace {
-class LayerImpl;
-class Frame;
-class DelayedEventGenerator;
-} // namespace
-
-namespace android {
-class SurfaceComposerClient;
-} // namespace android
-
-namespace android::hardware::graphics::composer::hal {
-
-using aidl::android::hardware::graphics::common::DisplayHotplugEvent;
-using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
-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(HWDisplayId display, Connection connection) override {
-        const auto event = connection == Connection::CONNECTED ? DisplayHotplugEvent::CONNECTED
-                                                               : DisplayHotplugEvent::DISCONNECTED;
-        mCallback->onComposerHalHotplugEvent(display, event);
-        return Void();
-    }
-
-    Return<void> onRefresh(HWDisplayId display) override {
-        mCallback->onComposerHalRefresh(display);
-        return Void();
-    }
-
-    Return<void> onVsync(HWDisplayId display, int64_t timestamp) override {
-        if (!mVsyncSwitchingSupported) {
-            mCallback->onComposerHalVsync(display, timestamp, std::nullopt);
-        }
-        return Void();
-    }
-
-    Return<void> onVsync_2_4(HWDisplayId display, int64_t timestamp,
-                             VsyncPeriodNanos vsyncPeriodNanos) override {
-        if (mVsyncSwitchingSupported) {
-            mCallback->onComposerHalVsync(display, timestamp, vsyncPeriodNanos);
-        }
-        return Void();
-    }
-
-    Return<void> onVsyncPeriodTimingChanged(HWDisplayId display,
-                                            const VsyncPeriodChangeTimeline& timeline) override {
-        mCallback->onComposerHalVsyncPeriodTimingChanged(display, timeline);
-        return Void();
-    }
-
-    Return<void> onSeamlessPossible(HWDisplayId display) override {
-        mCallback->onComposerHalSeamlessPossible(display);
-        return Void();
-    }
-
-private:
-    ComposerCallback* const mCallback;
-    const bool mVsyncSwitchingSupported;
-};
-
-struct TestHWC2ComposerCallback : public HWC2::ComposerCallback {
-    virtual ~TestHWC2ComposerCallback() = default;
-    void onComposerHalHotplugEvent(HWDisplayId, DisplayHotplugEvent) {}
-    void onComposerHalRefresh(HWDisplayId) {}
-    void onComposerHalVsync(HWDisplayId, int64_t, std::optional<VsyncPeriodNanos>) {}
-    void onComposerHalVsyncPeriodTimingChanged(HWDisplayId, const VsyncPeriodChangeTimeline&) {}
-    void onComposerHalSeamlessPossible(HWDisplayId) {}
-    void onComposerHalVsyncIdle(HWDisplayId) {}
-    void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) {}
-};
-
-} // namespace android::hardware::graphics::composer::hal
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
deleted file mode 100644
index ce8d47e..0000000
--- a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
+++ /dev/null
@@ -1,147 +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.
- *
- */
-
-#include <FrameTracer/FrameTracer.h>
-#include <fuzzer/FuzzedDataProvider.h>
-#include <perfetto/trace/trace.pb.h>
-
-namespace android::fuzz {
-
-using namespace google::protobuf;
-
-constexpr size_t kMaxStringSize = 256;
-constexpr size_t kMinLayerIds = 1;
-constexpr size_t kMaxLayerIds = 10;
-constexpr int32_t kMinRange = 0;
-constexpr int32_t kConfigDuration = 500;
-constexpr int32_t kBufferSize = 1024;
-constexpr int32_t kTimeOffset = 100000;
-
-class FrameTracerFuzzer {
-public:
-    FrameTracerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) {
-        // Fuzzer is single-threaded, so no need to be thread-safe.
-        static bool wasInitialized = false;
-        if (!wasInitialized) {
-            perfetto::TracingInitArgs args;
-            args.backends = perfetto::kInProcessBackend;
-            perfetto::Tracing::Initialize(args);
-            wasInitialized = true;
-        }
-        mFrameTracer = std::make_unique<android::FrameTracer>();
-    }
-    ~FrameTracerFuzzer() { mFrameTracer.reset(); }
-    void process();
-
-private:
-    void traceTimestamp();
-    void traceTimestamp(std::vector<int32_t> layerIds, size_t numLayerIds);
-    void traceFence(std::vector<int32_t> layerIds, size_t numLayerIds);
-    std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest();
-    std::unique_ptr<android::FrameTracer> mFrameTracer = nullptr;
-    std::vector<int32_t> generateLayerIds(size_t numLayerIds);
-    android::FenceToFenceTimeMap mFenceFactory;
-    FuzzedDataProvider mFdp;
-};
-
-std::unique_ptr<perfetto::TracingSession> FrameTracerFuzzer::getTracingSessionForTest() {
-    perfetto::TraceConfig cfg;
-    cfg.set_duration_ms(mFdp.ConsumeIntegralInRange<int32_t>(kMinRange, kConfigDuration));
-    cfg.add_buffers()->set_size_kb(mFdp.ConsumeIntegralInRange<int32_t>(kMinRange, kBufferSize));
-    auto* dsCfg = cfg.add_data_sources()->mutable_config();
-    dsCfg->set_name(android::FrameTracer::kFrameTracerDataSource);
-
-    auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
-    tracingSession->Setup(cfg);
-    return tracingSession;
-}
-
-std::vector<int32_t> FrameTracerFuzzer::generateLayerIds(size_t numLayerIds) {
-    std::vector<int32_t> layerIds;
-    for (size_t i = 0; i < numLayerIds; ++i) {
-        layerIds.push_back(mFdp.ConsumeIntegral<int32_t>());
-    }
-    return layerIds;
-}
-
-void FrameTracerFuzzer::traceTimestamp(std::vector<int32_t> layerIds, size_t numLayerIds) {
-    uint32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange<size_t>(0, numLayerIds - 1));
-    android::FrameTracer::FrameEvent::BufferEventType type = static_cast<
-            android::FrameTracer::FrameEvent::BufferEventType>(
-            mFdp.ConsumeIntegralInRange<uint32_t>(android::FrameTracer::FrameEvent::UNSPECIFIED,
-                                                  android::FrameTracer::FrameEvent::CANCEL));
-    mFrameTracer->traceTimestamp(layerId, mFdp.ConsumeIntegral<uint64_t>() /*bufferID*/,
-                                 mFdp.ConsumeIntegral<uint64_t>() /*frameNumber*/,
-                                 mFdp.ConsumeIntegral<nsecs_t>() /*timestamp*/, type,
-                                 mFdp.ConsumeIntegral<nsecs_t>() /*duration*/);
-}
-
-void FrameTracerFuzzer::traceFence(std::vector<int32_t> layerIds, size_t numLayerIds) {
-    const nsecs_t signalTime =
-            mFdp.ConsumeBool() ? android::Fence::SIGNAL_TIME_PENDING : systemTime();
-    const nsecs_t startTime = (signalTime == android::Fence::SIGNAL_TIME_PENDING)
-            ? signalTime - kTimeOffset
-            : signalTime + kTimeOffset;
-    auto fence = mFenceFactory.createFenceTimeForTest(android::Fence::NO_FENCE);
-    mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, signalTime);
-    int32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange<size_t>(0, numLayerIds - 1));
-    mFrameTracer->traceFence(layerId, mFdp.ConsumeIntegral<uint64_t>() /*bufferID*/,
-                             mFdp.ConsumeIntegral<uint64_t>() /*frameNumber*/, fence,
-                             android::FrameTracer::FrameEvent::ACQUIRE_FENCE, startTime);
-}
-
-void FrameTracerFuzzer::process() {
-    std::vector<int32_t> layerIds =
-            generateLayerIds(mFdp.ConsumeIntegralInRange<size_t>(kMinLayerIds, kMaxLayerIds));
-
-    std::unique_ptr<perfetto::TracingSession> tracingSession;
-    while (mFdp.remaining_bytes()) {
-        auto invokeFrametracerAPI = mFdp.PickValueInArray<const std::function<void()>>({
-                [&]() { mFrameTracer->registerDataSource(); },
-                [&]() {
-                    if (tracingSession) {
-                        tracingSession->StopBlocking();
-                    }
-                    tracingSession = getTracingSessionForTest();
-                    tracingSession->StartBlocking();
-                },
-                [&]() { traceTimestamp(layerIds, layerIds.size()); },
-                [&]() { traceFence(layerIds, layerIds.size()); },
-                [&]() {
-                    for (auto it = layerIds.begin(); it != layerIds.end(); ++it) {
-                        mFrameTracer->traceNewLayer(*it /*layerId*/,
-                                                    mFdp.ConsumeRandomLengthString(
-                                                            kMaxStringSize) /*layerName*/);
-                    }
-                },
-                [&]() { mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, systemTime()); },
-        });
-        invokeFrametracerAPI();
-    }
-
-    for (auto it = layerIds.begin(); it != layerIds.end(); ++it) {
-        mFrameTracer->onDestroy(*it);
-    }
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    FrameTracerFuzzer frameTracerFuzzer(data, size);
-    frameTracerFuzzer.process();
-    return 0;
-}
-
-} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
deleted file mode 100644
index d13189e..0000000
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
+++ /dev/null
@@ -1,257 +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.
- *
- */
-
-#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::AutoSingleLayer,
-        LatchUnsignaledConfig::Disabled,
-};
-
-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,
-        BnSurfaceComposer::GET_SCHEDULING_POLICY,
-};
-
-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 = sp<SurfaceFlinger>::fromExisting(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>();
-    mTestableFlinger.mutableSupportsWideColor() = mFdp.ConsumeBool();
-    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);
-
-    using FrameHint = SurfaceFlinger::FrameHint;
-    mFlinger->scheduleComposite(mFdp.ConsumeBool() ? FrameHint::kActive : FrameHint::kNone);
-    mFlinger->scheduleRepaint();
-    mFlinger->scheduleSample();
-
-    sp<IBinder> handle = defaultServiceManager()->checkService(
-            String16(mFdp.ConsumeRandomLengthString().c_str()));
-    LayerHandle::getLayer(handle);
-    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>();
-    std::vector<uint64_t> mergedTransactionIds{};
-
-    mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken,
-                                         InputWindowCommands{}, desiredPresentTime, isAutoTimestamp,
-                                         {}, hasListenerCallbacks, listenerCallbacks, transactionId,
-                                         mergedTransactionIds);
-}
-
-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());
-
-    FTL_FAKE_GUARD(kMainThreadContext,
-                   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
deleted file mode 100644
index 81fbc84..0000000
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ /dev/null
@@ -1,818 +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 <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 <ftl/fake_guard.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 "DisplayDevice.h"
-#include "DisplayHardware/ComposerHal.h"
-#include "FrameTimeline/FrameTimeline.h"
-#include "FrameTracer/FrameTracer.h"
-#include "FrontEnd/LayerHandle.h"
-#include "Layer.h"
-#include "NativeWindowSurface.h"
-#include "Scheduler/EventThread.h"
-#include "Scheduler/MessageQueue.h"
-#include "Scheduler/RefreshRateSelector.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 "ThreadContext.h"
-#include "TimeStats/TimeStats.h"
-#include "surfaceflinger_scheduler_fuzzer.h"
-
-#include "renderengine/mock/RenderEngine.h"
-#include "scheduler/TimeKeeper.h"
-#include "tests/unittests/mock/DisplayHardware/MockComposer.h"
-#include "tests/unittests/mock/DisplayHardware/MockDisplayMode.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/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::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};
-
-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};
-
-inline VsyncId getFuzzedVsyncId(FuzzedDataProvider& fdp) {
-    return VsyncId{fdp.ConsumeIntegral<int64_t>()};
-}
-
-inline TimePoint getFuzzedTimePoint(FuzzedDataProvider& fdp) {
-    return TimePoint::fromNs(fdp.ConsumeIntegral<nsecs_t>());
-}
-
-inline Duration getFuzzedDuration(FuzzedDataProvider& fdp) {
-    return Duration::fromNs(fdp.ConsumeIntegral<nsecs_t>());
-}
-
-inline FloatRect getFuzzedFloatRect(FuzzedDataProvider* fdp) {
-    return FloatRect(fdp->ConsumeFloatingPoint<float>() /*left*/,
-                     fdp->ConsumeFloatingPoint<float>() /*right*/,
-                     fdp->ConsumeFloatingPoint<float>() /*top*/,
-                     fdp->ConsumeFloatingPoint<float>() /*bottom*/);
-}
-
-inline 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);
-
-    scheduler::VsyncConfigSet getConfigsForRefreshRate(Fps) const override {
-        return getCurrentConfigs();
-    }
-
-    scheduler::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::RefreshRateSelector>& selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback,
-                      IVsyncTrackerCallback& vsyncTrackerCallback)
-          : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
-                              std::make_shared<android::mock::VSyncTracker>(), selectorPtr,
-                              std::move(modulatorPtr), callback, vsyncTrackerCallback) {}
-
-    TestableScheduler(std::unique_ptr<VsyncController> controller,
-                      VsyncSchedule::TrackerPtr tracker,
-                      std::shared_ptr<RefreshRateSelector> selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback,
-                      IVsyncTrackerCallback& vsyncTrackerCallback)
-          : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr),
-                      vsyncTrackerCallback) {
-        const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
-        registerDisplayInternal(displayId, std::move(selectorPtr),
-                                std::shared_ptr<VsyncSchedule>(
-                                        new VsyncSchedule(displayId, std::move(tracker),
-                                                          std::make_shared<FuzzImplVSyncDispatch>(),
-                                                          std::move(controller))));
-    }
-
-    ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
-        return Scheduler::createConnection(std::move(eventThread));
-    }
-
-    auto &mutableLayerHistory() { return mLayerHistory; }
-
-    auto refreshRateSelector() { return pacesetterSelectorPtr(); }
-
-    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, const FrameRateMode &mode) {
-        return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
-    }
-
-    using Scheduler::setVsyncConfig;
-
-private:
-    // ICompositor overrides:
-    void configure() override {}
-    bool commit(PhysicalDisplayId, const scheduler::FrameTargets&) override { return false; }
-    CompositeResultsPerDisplay composite(PhysicalDisplayId,
-                                         const scheduler::FrameTargeters&) override {
-        return {};
-    }
-    void sample() override {}
-
-    // MessageQueue overrides:
-    void scheduleFrame() override {}
-    void postMessage(sp<MessageHandler>&& handler) override { handler->handleMessage(Message()); }
-};
-
-} // namespace scheduler
-
-namespace surfaceflinger::test {
-
-class Factory final : public surfaceflinger::Factory {
-    struct NoOpMessageQueue : android::impl::MessageQueue {
-        using android::impl::MessageQueue::MessageQueue;
-        void onFrameSignal(ICompositor&, VsyncId, TimePoint) override {}
-    };
-
-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<NoOpMessageQueue>(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::RefreshRateSelector>&,
-            scheduler::ISchedulerCallback&) {
-        return nullptr;
-    }
-
-    sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
-        return sp<StartPropertySetThread>::make(timestampPropertyValue);
-    }
-
-    sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs &creationArgs) override {
-        return sp<DisplayDevice>::make(creationArgs);
-    }
-
-    sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format,
-                                          uint32_t layerCount, uint64_t usage,
-                                          std::string requestorName) override {
-        return sp<GraphicBuffer>::make(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);
-    }
-
-    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<Layer> createBufferStateLayer(const LayerCreationArgs &) override { return nullptr; }
-
-    sp<Layer> createEffectLayer(const LayerCreationArgs &args) override {
-        return sp<Layer>::make(args);
-    }
-
-    sp<LayerFE> createLayerFE(const std::string &layerName) override {
-        return sp<LayerFE>::make(layerName);
-    }
-
-    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,
-                                     private scheduler::IVsyncTrackerCallback {
-public:
-    using HotplugEvent = SurfaceFlinger::HotplugEvent;
-
-    SurfaceFlinger *flinger() { return mFlinger.get(); }
-    scheduler::TestableScheduler *scheduler() { return mScheduler; }
-
-    void initializeDisplays() {
-        FTL_FAKE_GUARD(kMainThreadContext, mFlinger->initializeDisplays());
-    }
-
-    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::vector<uint8_t> pulledData = fdp->ConsumeRemainingBytes<uint8_t>();
-        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);
-        FTL_FAKE_GUARD(kMainThreadContext,
-                       mFlinger->logFrameStats(TimePoint::fromNs(fdp->ConsumeIntegral<nsecs_t>())));
-
-        result = fdp->ConsumeRandomLengthString().c_str();
-        mFlinger->dumpFrameTimeline(dumpArgs, result);
-
-        result = fdp->ConsumeRandomLengthString().c_str();
-        mFlinger->dumpRawDisplayIdentificationData(dumpArgs, result);
-
-        perfetto::protos::LayersProto layersProto =
-                mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral<uint32_t>());
-        mFlinger->dumpOffscreenLayersProto(layersProto);
-        mFlinger->dumpDisplayProto();
-
-        result = fdp->ConsumeRandomLengthString().c_str();
-        mFlinger->dumpHwc(result);
-
-        mFlinger->calculateColorMatrix(fdp->ConsumeFloatingPoint<float>());
-        mFlinger->updateColorMatrixLocked();
-        mFlinger->CheckTransactCodeCredentials(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(const sp<IBinder>& display, FuzzedDataProvider* fdp) {
-        std::vector<ui::Hdr> hdrTypes;
-        hdrTypes.push_back(fdp->PickValueInArray(kHdrTypes));
-        mFlinger->overrideHdrTypes(display, hdrTypes);
-    }
-
-    void getDisplayedContentSample(const sp<IBinder>& display, FuzzedDataProvider* fdp) {
-        DisplayedFrameStats outDisplayedFrameStats;
-        mFlinger->getDisplayedContentSample(display, fdp->ConsumeIntegral<uint64_t>(),
-                                            fdp->ConsumeIntegral<uint64_t>(),
-                                            &outDisplayedFrameStats);
-    }
-
-    void getDisplayStats(const sp<IBinder>& display) {
-        android::DisplayStatInfo stats;
-        mFlinger->getDisplayStats(display, &stats);
-    }
-
-    void getDisplayState(const sp<IBinder>& display) {
-        ui::DisplayState displayState;
-        mFlinger->getDisplayState(display, &displayState);
-    }
-
-    void getStaticDisplayInfo(int64_t displayId) {
-        ui::StaticDisplayInfo staticDisplayInfo;
-        mFlinger->getStaticDisplayInfo(displayId, &staticDisplayInfo);
-    }
-
-    void getDynamicDisplayInfo(int64_t displayId) {
-        android::ui::DynamicDisplayInfo dynamicDisplayInfo;
-        mFlinger->getDynamicDisplayInfoFromId(displayId, &dynamicDisplayInfo);
-    }
-    void getDisplayNativePrimaries(const sp<IBinder>& display) {
-        android::ui::DisplayPrimaries displayPrimaries;
-        mFlinger->getDisplayNativePrimaries(display, displayPrimaries);
-    }
-
-    void getDesiredDisplayModeSpecs(const sp<IBinder>& display) {
-        gui::DisplayModeSpecs _;
-        mFlinger->getDesiredDisplayModeSpecs(display, &_);
-    }
-
-    // TODO(b/248317436): extend to cover all displays for multi-display devices
-    static std::optional<PhysicalDisplayId> getFirstDisplayId() {
-        std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
-        if (ids.empty()) return {};
-        return ids.front();
-    }
-
-    std::pair<sp<IBinder>, PhysicalDisplayId> fuzzBoot(FuzzedDataProvider* fdp) {
-        mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool());
-        const sp<Client> client = sp<Client>::make(mFlinger);
-
-        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 = getFirstDisplayId().value_or(
-                PhysicalDisplayId::fromPort(fdp->ConsumeIntegral<uint8_t>()));
-        mFlinger->getHwComposer().allocatePhysicalDisplay(kHwDisplayId, physicalDisplayId);
-
-        sp<IBinder> display =
-                mFlinger->createDisplay(String8(fdp->ConsumeRandomLengthString().c_str()),
-                                        fdp->ConsumeBool());
-
-        initializeDisplays();
-        mFlinger->getPhysicalDisplayToken(physicalDisplayId);
-
-        mFlinger->mStartPropertySetThread =
-                mFlinger->getFactory().createStartPropertySetThread(fdp->ConsumeBool());
-
-        mFlinger->bootFinished();
-
-        return {display, physicalDisplayId};
-    }
-
-    void fuzzSurfaceFlinger(const uint8_t *data, size_t size) {
-        FuzzedDataProvider mFdp(data, size);
-
-        const auto [display, displayId] = fuzzBoot(&mFdp);
-
-        sp<IGraphicBufferProducer> bufferProducer = sp<mock::GraphicBufferProducer>::make();
-
-        mFlinger->createDisplayEventConnection();
-
-        getDisplayStats(display);
-        getDisplayState(display);
-        getStaticDisplayInfo(displayId.value);
-        getDynamicDisplayInfo(displayId.value);
-        getDisplayNativePrimaries(display);
-
-        mFlinger->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
-        mFlinger->setGameContentType(display, mFdp.ConsumeBool());
-        mFlinger->setPowerMode(display, mFdp.ConsumeIntegral<int>());
-
-        overrideHdrTypes(display, &mFdp);
-
-        onPullAtom(&mFdp);
-
-        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->updateInputFlinger(VsyncId{}, TimePoint{});
-        mFlinger->updateCursorAsync();
-
-        mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral<nsecs_t>(),
-                                           .appOffset = mFdp.ConsumeIntegral<nsecs_t>(),
-                                           .sfWorkDuration = getFuzzedDuration(mFdp),
-                                           .appWorkDuration = getFuzzedDuration(mFdp)},
-                                          getFuzzedDuration(mFdp));
-
-        {
-            ftl::FakeGuard guard(kMainThreadContext);
-
-            mFlinger->commitTransactionsLegacy();
-            mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp));
-
-            scheduler::FrameTargeter frameTargeter(displayId, mFdp.ConsumeBool());
-            mFlinger->onCompositionPresented(displayId, ftl::init::map(displayId, &frameTargeter),
-                                             mFdp.ConsumeIntegral<nsecs_t>());
-        }
-
-        mFlinger->setTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
-        mFlinger->clearTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
-        mFlinger->commitOffscreenLayers();
-
-        mFlinger->frameIsEarly(getFuzzedTimePoint(mFdp), getFuzzedVsyncId(mFdp));
-        mFlinger->computeLayerBounds();
-        mFlinger->startBootAnim();
-
-        mFlinger->readPersistentProperties();
-
-        mFlinger->exceedsMaxRenderTargetSize(mFdp.ConsumeIntegral<uint32_t>(),
-                                             mFdp.ConsumeIntegral<uint32_t>());
-
-        mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral<uid_t>());
-
-        mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool());
-
-        fuzzDumpsysAndDebug(&mFdp);
-
-        mFlinger->destroyDisplay(display);
-    }
-
-    void setupRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
-        mFlinger->mRenderEngine = std::move(renderEngine);
-        mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
-    }
-
-    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::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
-                        std::unique_ptr<EventThread> appEventThread,
-                        std::unique_ptr<EventThread> sfEventThread,
-                        scheduler::ISchedulerCallback* callback = nullptr,
-                        scheduler::IVsyncTrackerCallback* vsyncTrackerCallback = nullptr,
-                        bool hasMultipleModes = false) {
-        constexpr DisplayModeId kModeId60{0};
-        DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
-
-        if (hasMultipleModes) {
-            constexpr DisplayModeId kModeId90{1};
-            modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz));
-        }
-
-        mRefreshRateSelector = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
-        const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getVsyncRate();
-        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
-
-        mFlinger->mRefreshRateStats =
-                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
-                                                              hal::PowerMode::OFF);
-
-        auto modulatorPtr = sp<scheduler::VsyncModulator>::make(
-                mFlinger->mVsyncConfiguration->getCurrentConfigs());
-
-        mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
-                                                      std::move(vsyncTracker), mRefreshRateSelector,
-                                                      std::move(modulatorPtr), *(callback ?: this),
-                                                      *(vsyncTrackerCallback ?: 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); }
-
-    void commitTransactionsLocked(uint32_t transactionFlags) FTL_FAKE_GUARD(kMainThreadContext) {
-        Mutex::Autolock lock(mFlinger->mStateLock);
-        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 &getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; }
-    auto &getPendingTransactionQueue() {
-        return mFlinger->mTransactionHandler.mPendingTransactionQueues;
-    }
-
-    auto setTransactionState(
-            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
-            const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
-            bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
-            bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
-            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) {
-        return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
-                                             inputWindowCommands, desiredPresentTime,
-                                             isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
-                                             listenerCallbacks, transactionId,
-                                             mergedTransactionIds);
-    }
-
-    auto flushTransactionQueues() {
-        ftl::FakeGuard guard(kMainThreadContext);
-        return mFlinger->flushTransactionQueues(VsyncId{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& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; }
-    auto& mutableCurrentState() { return mFlinger->mCurrentState; }
-    auto& mutableDisplays() { return mFlinger->mDisplays; }
-    auto& mutableDrawingState() { return mFlinger->mDrawingState; }
-
-    auto fromHandle(const sp<IBinder> &handle) { return LayerHandle::getLayer(handle); }
-
-    ~TestableSurfaceFlinger() {
-        mutableDisplays().clear();
-        mutableCurrentState().displays.clear();
-        mutableDrawingState().displays.clear();
-        mFlinger->mScheduler.reset();
-        mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
-        mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
-        mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
-    }
-
-private:
-    void requestHardwareVsync(PhysicalDisplayId, bool) override {}
-    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
-    void kernelTimerChanged(bool) override {}
-    void triggerOnFrameRateOverridesChanged() override {}
-    void onChoreographerAttached() override {}
-
-    // IVsyncTrackerCallback overrides
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
-
-    surfaceflinger::test::Factory mFactory;
-    sp<SurfaceFlinger> mFlinger =
-            sp<SurfaceFlinger>::make(mFactory, SurfaceFlinger::SkipInitialization);
-    scheduler::TestableScheduler *mScheduler = nullptr;
-    std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
-};
-
-} // namespace android
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
deleted file mode 100644
index 7aae3c4..0000000
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ /dev/null
@@ -1,206 +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.
- *
- */
-#include <Client.h>
-#include <DisplayDevice.h>
-#include <LayerRenderArea.h>
-#include <ftl/future.h>
-#include <fuzzer/FuzzedDataProvider.h>
-#include <gui/IProducerListener.h>
-#include <gui/LayerDebugInfo.h>
-#include <gui/SurfaceComposerClient.h>
-#include <gui/WindowInfo.h>
-#include <renderengine/mock/FakeExternalTexture.h>
-#include <ui/DisplayStatInfo.h>
-#include <ui/Transform.h>
-
-#include <FuzzableDataspaces.h>
-#include <surfaceflinger_fuzzers_utils.h>
-
-namespace android::fuzzer {
-using namespace renderengine;
-
-constexpr uint16_t kRandomStringLength = 256;
-
-class LayerFuzzer {
-public:
-    LayerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void init();
-    void invokeBufferStateLayer();
-    void invokeEffectLayer();
-    LayerCreationArgs createLayerCreationArgs(TestableSurfaceFlinger* flinger, sp<Client> client);
-    Rect getFuzzedRect();
-    ui::Transform getFuzzedTransform();
-    FrameTimelineInfo getFuzzedFrameTimelineInfo();
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-Rect LayerFuzzer::getFuzzedRect() {
-    return Rect(mFdp.ConsumeIntegral<int32_t>() /*left*/, mFdp.ConsumeIntegral<int32_t>() /*top*/,
-                mFdp.ConsumeIntegral<int32_t>() /*right*/,
-                mFdp.ConsumeIntegral<int32_t>() /*bottom*/);
-}
-
-ui::Transform LayerFuzzer::getFuzzedTransform() {
-    return ui::Transform(mFdp.ConsumeIntegral<int32_t>() /*orientation*/,
-                         mFdp.ConsumeIntegral<int32_t>() /*width*/,
-                         mFdp.ConsumeIntegral<int32_t>() /*height*/);
-}
-
-FrameTimelineInfo LayerFuzzer::getFuzzedFrameTimelineInfo() {
-    FrameTimelineInfo ftInfo;
-    ftInfo.vsyncId = mFdp.ConsumeIntegral<int64_t>();
-    ftInfo.inputEventId = mFdp.ConsumeIntegral<int32_t>();
-    return ftInfo;
-}
-
-LayerCreationArgs LayerFuzzer::createLayerCreationArgs(TestableSurfaceFlinger* flinger,
-                                                       sp<Client> client) {
-    flinger->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>());
-
-    return LayerCreationArgs(flinger->flinger(), client,
-                             mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/,
-                             mFdp.ConsumeIntegral<uint32_t>() /*flags*/, {} /*metadata*/);
-}
-
-void LayerFuzzer::invokeEffectLayer() {
-    TestableSurfaceFlinger flinger;
-    sp<Client> client = sp<Client>::make(sp<SurfaceFlinger>::fromExisting(flinger.flinger()));
-    const LayerCreationArgs layerCreationArgs = createLayerCreationArgs(&flinger, client);
-    sp<Layer> effectLayer = sp<Layer>::make(layerCreationArgs);
-
-    effectLayer->setColor({(mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*x*/,
-                            mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*y*/,
-                            mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*z*/)});
-    effectLayer->setDataspace(mFdp.PickValueInArray(kDataspaces));
-    sp<Layer> parent = sp<Layer>::make(layerCreationArgs);
-    effectLayer->setChildrenDrawingParent(parent);
-
-    const FrameTimelineInfo frameInfo = getFuzzedFrameTimelineInfo();
-    const int64_t postTime = mFdp.ConsumeIntegral<int64_t>();
-    effectLayer->setFrameTimelineVsyncForBufferTransaction(frameInfo, postTime);
-    effectLayer->setFrameTimelineVsyncForBufferlessTransaction(frameInfo, postTime);
-    auto surfaceFrame = effectLayer->createSurfaceFrameForTransaction(frameInfo, postTime);
-    auto surfaceFrame1 =
-            effectLayer->createSurfaceFrameForBuffer(frameInfo, postTime,
-                                                     mFdp.ConsumeRandomLengthString(
-                                                             kRandomStringLength) /*bufferName*/);
-    effectLayer->addSurfaceFramePresentedForBuffer(surfaceFrame,
-                                                   mFdp.ConsumeIntegral<int64_t>() /*acquireTime*/,
-                                                   mFdp.ConsumeIntegral<int64_t>() /*currentTime*/);
-    effectLayer->addSurfaceFrameDroppedForBuffer(surfaceFrame1, mFdp.ConsumeIntegral<nsecs_t>());
-
-    parent.clear();
-    client.clear();
-    effectLayer.clear();
-}
-
-void LayerFuzzer::invokeBufferStateLayer() {
-    TestableSurfaceFlinger flinger;
-    sp<Client> client = sp<Client>::make(sp<SurfaceFlinger>::fromExisting(flinger.flinger()));
-    sp<Layer> layer = sp<Layer>::make(createLayerCreationArgs(&flinger, client));
-    sp<Fence> fence = sp<Fence>::make();
-    const std::shared_ptr<FenceTime> fenceTime = std::make_shared<FenceTime>(fence);
-
-    const CompositorTiming compositorTiming(mFdp.ConsumeIntegral<int64_t>(),
-                                            mFdp.ConsumeIntegral<int64_t>(),
-                                            mFdp.ConsumeIntegral<int64_t>(),
-                                            mFdp.ConsumeIntegral<int64_t>());
-
-    layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
-                            ui::LayerStack::fromValue(mFdp.ConsumeIntegral<uint32_t>()));
-    layer->onLayerDisplayed(ftl::yield<FenceResult>(
-                                    base::unexpected(mFdp.ConsumeIntegral<status_t>()))
-                                    .share(),
-                            ui::LayerStack::fromValue(mFdp.ConsumeIntegral<uint32_t>()));
-
-    layer->releasePendingBuffer(mFdp.ConsumeIntegral<int64_t>());
-    layer->onCompositionPresented(nullptr, fenceTime, fenceTime, compositorTiming);
-
-    layer->setTransform(mFdp.ConsumeIntegral<uint32_t>());
-    layer->setTransformToDisplayInverse(mFdp.ConsumeBool());
-    layer->setCrop(getFuzzedRect());
-
-    layer->setHdrMetadata(getFuzzedHdrMetadata(&mFdp));
-    layer->setDataspace(mFdp.PickValueInArray(kDataspaces));
-    if (mFdp.ConsumeBool()) {
-        layer->setSurfaceDamageRegion(Region());
-        layer->setTransparentRegionHint(Region());
-    } else {
-        layer->setSurfaceDamageRegion(Region(getFuzzedRect()));
-        layer->setTransparentRegionHint(Region(getFuzzedRect()));
-    }
-    layer->setApi(mFdp.ConsumeIntegral<int32_t>());
-
-    native_handle_t* testHandle = native_handle_create(0, 1);
-    const bool ownsHandle = mFdp.ConsumeBool();
-    sp<NativeHandle> nativeHandle = sp<NativeHandle>::make(testHandle, ownsHandle);
-    layer->setSidebandStream(nativeHandle, getFuzzedFrameTimelineInfo(),
-                             mFdp.ConsumeIntegral<nsecs_t>() /* postTime */);
-    layer->computeSourceBounds(getFuzzedFloatRect(&mFdp));
-
-    layer->fenceHasSignaled();
-    layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>());
-    const std::vector<sp<CallbackHandle>> callbacks;
-    layer->setTransactionCompletedListeners(callbacks, mFdp.ConsumeBool());
-
-    std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
-            renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral<uint32_t>(),
-                                                     mFdp.ConsumeIntegral<uint32_t>(),
-                                                     mFdp.ConsumeIntegral<uint64_t>(),
-                                                     static_cast<android::PixelFormat>(
-                                                             mFdp.PickValueInArray(kPixelFormats)),
-                                                     mFdp.ConsumeIntegral<uint64_t>());
-    layer->setBuffer(texture, {} /*bufferData*/, mFdp.ConsumeIntegral<nsecs_t>() /*postTime*/,
-                     mFdp.ConsumeIntegral<nsecs_t>() /*desiredTime*/,
-                     mFdp.ConsumeBool() /*isAutoTimestamp*/,
-                     {mFdp.ConsumeIntegral<nsecs_t>()} /*dequeue*/, {} /*info*/);
-
-    LayerRenderArea layerArea(*(flinger.flinger()), layer, getFuzzedRect(),
-                              {mFdp.ConsumeIntegral<int32_t>(),
-                               mFdp.ConsumeIntegral<int32_t>()} /*reqSize*/,
-                              mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(),
-                              mFdp.ConsumeBool(), getFuzzedTransform(), getFuzzedRect(),
-                              mFdp.ConsumeBool());
-    layerArea.render([]() {} /*drawLayers*/);
-
-    if (!ownsHandle) {
-        native_handle_close(testHandle);
-        native_handle_delete(testHandle);
-    }
-    nativeHandle.clear();
-    fence.clear();
-    client.clear();
-    layer.clear();
-}
-
-void LayerFuzzer::init() {
-    invokeBufferStateLayer();
-    invokeEffectLayer();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    LayerFuzzer layerFuzzer(data, size);
-    layerFuzzer.init();
-    return 0;
-}
-
-} // namespace android::fuzzer
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
deleted file mode 100644
index b690d8d..0000000
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ /dev/null
@@ -1,474 +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.
- *
- */
-
-#include <ftl/enum.h>
-#include <fuzzer/FuzzedDataProvider.h>
-#include <processgroup/sched_policy.h>
-
-#include <scheduler/IVsyncSource.h>
-#include <scheduler/PresentLatencyTracker.h>
-
-#include "Scheduler/OneShotTimer.h"
-#include "Scheduler/RefreshRateSelector.h"
-#include "Scheduler/VSyncDispatchTimerQueue.h"
-#include "Scheduler/VSyncPredictor.h"
-#include "Scheduler/VSyncReactor.h"
-
-#include "mock/DisplayHardware/MockDisplayMode.h"
-#include "mock/MockVSyncDispatch.h"
-#include "mock/MockVSyncTracker.h"
-
-#include "surfaceflinger_fuzzers_utils.h"
-#include "surfaceflinger_scheduler_fuzzer.h"
-
-namespace android::fuzz {
-
-using hardware::graphics::composer::hal::PowerMode;
-
-constexpr nsecs_t kVsyncPeriods[] = {(30_Hz).getPeriodNsecs(), (60_Hz).getPeriodNsecs(),
-                                     (72_Hz).getPeriodNsecs(), (90_Hz).getPeriodNsecs(),
-                                     (120_Hz).getPeriodNsecs()};
-
-constexpr auto kLayerVoteTypes = ftl::enum_range<scheduler::RefreshRateSelector::LayerVoteType>();
-constexpr auto kCompositionCoverage = ftl::enum_range<CompositionCoverage>();
-
-constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode::OFF,
-                                     PowerMode::DOZE_SUSPEND, PowerMode::ON_SUSPEND};
-
-constexpr uint16_t kRandomStringLength = 256;
-constexpr std::chrono::duration kSyncPeriod(16ms);
-constexpr PhysicalDisplayId kDisplayId = PhysicalDisplayId::fromPort(42u);
-
-template <typename T>
-void dump(T* component, FuzzedDataProvider* fdp) {
-    std::string res = fdp->ConsumeRandomLengthString(kRandomStringLength);
-    component->dump(res);
-}
-
-inline sp<Fence> makeFakeFence() {
-    return sp<Fence>::make(memfd_create("fd", MFD_ALLOW_SEALING));
-}
-
-class SchedulerFuzzer {
-public:
-    SchedulerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    void fuzzRefreshRateSelection();
-    void fuzzRefreshRateSelector();
-    void fuzzPresentLatencyTracker();
-    void fuzzFrameTargeter();
-    void fuzzVSyncModulator();
-    void fuzzVSyncPredictor();
-    void fuzzVSyncReactor();
-    void fuzzLayerHistory();
-    void fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch);
-    void fuzzVSyncDispatchTimerQueue();
-    void fuzzOneShotTimer();
-    void fuzzEventThread();
-    PhysicalDisplayId getPhysicalDisplayId();
-
-    FuzzedDataProvider mFdp;
-
-    std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
-};
-
-PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() {
-    PhysicalDisplayId internalDispId = PhysicalDisplayId::fromPort(111u);
-    PhysicalDisplayId externalDispId = PhysicalDisplayId::fromPort(222u);
-    PhysicalDisplayId randomDispId = PhysicalDisplayId::fromPort(mFdp.ConsumeIntegral<uint16_t>());
-    PhysicalDisplayId dispId64Bit = PhysicalDisplayId::fromEdid(0xffu, 0xffffu, 0xffff'ffffu);
-    PhysicalDisplayId displayId = mFdp.PickValueInArray<PhysicalDisplayId>(
-            {internalDispId, externalDispId, dispId64Bit, randomDispId});
-    return displayId;
-}
-
-struct EventThreadCallback : public IEventThreadCallback {
-    bool throttleVsync(TimePoint, uid_t) override { return false; }
-    Period getVsyncPeriod(uid_t) override { return kSyncPeriod; }
-    void resync() override {}
-};
-
-void SchedulerFuzzer::fuzzEventThread() {
-    mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>(
-            new scheduler::VsyncSchedule(getPhysicalDisplayId(),
-                                         std::make_shared<mock::VSyncTracker>(),
-                                         std::make_shared<mock::VSyncDispatch>(), nullptr));
-    EventThreadCallback callback;
-    std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
-            android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, callback,
-                                        (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
-                                        (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
-
-    thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool());
-    sp<EventThreadConnection> connection =
-            sp<EventThreadConnection>::make(thread.get(), mFdp.ConsumeIntegral<uint16_t>());
-    thread->requestNextVsync(connection);
-    thread->setVsyncRate(mFdp.ConsumeIntegral<uint32_t>() /*rate*/, connection);
-
-    thread->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
-                        (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
-    thread->registerDisplayEventConnection(connection);
-    thread->enableSyntheticVsync(mFdp.ConsumeBool());
-    dump<android::impl::EventThread>(thread.get(), &mFdp);
-}
-
-void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch) {
-    scheduler::VSyncDispatch::CallbackToken tmp = dispatch->registerCallback(
-            [&](auto, auto, auto) {
-                dispatch->schedule(tmp,
-                                   {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                                    .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
-            },
-            "o.o");
-    dispatch->schedule(tmp,
-                       {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                        .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                        .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
-    dispatch->unregisterCallback(tmp);
-    dispatch->cancel(tmp);
-}
-
-void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() {
-    auto stubTracker = std::make_shared<FuzzImplVSyncTracker>(mFdp.ConsumeIntegral<nsecs_t>());
-    scheduler::VSyncDispatchTimerQueue
-            mDispatch{std::make_unique<scheduler::ControllableClock>(), stubTracker,
-                      mFdp.ConsumeIntegral<nsecs_t>() /*dispatchGroupThreshold*/,
-                      mFdp.ConsumeIntegral<nsecs_t>() /*vSyncMoveThreshold*/};
-
-    fuzzCallbackToken(&mDispatch);
-
-    dump<scheduler::VSyncDispatchTimerQueue>(&mDispatch, &mFdp);
-
-    scheduler::VSyncDispatchTimerQueueEntry entry(
-            "fuzz", [](auto, auto, auto) {},
-            mFdp.ConsumeIntegral<nsecs_t>() /*vSyncMoveThreshold*/);
-    entry.update(*stubTracker, 0);
-    entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                    .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
-                   *stubTracker, 0);
-    entry.disarm();
-    entry.ensureNotRunning();
-    entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                    .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
-                   *stubTracker, 0);
-    auto const wakeup = entry.wakeupTime();
-    auto const ready = entry.readyTime();
-    entry.callback(entry.executing(), *wakeup, *ready);
-    entry.addPendingWorkloadUpdate({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                                    .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
-    dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp);
-}
-
-struct VsyncTrackerCallback : public scheduler::IVsyncTrackerCallback {
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
-};
-
-void SchedulerFuzzer::fuzzVSyncPredictor() {
-    uint16_t now = mFdp.ConsumeIntegral<uint16_t>();
-    uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
-    uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
-    nsecs_t idealPeriod = mFdp.ConsumeIntegralInRange<nsecs_t>(1, UINT32_MAX);
-    VsyncTrackerCallback callback;
-    const auto mode = ftl::as_non_null(
-            mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(idealPeriod)));
-    scheduler::VSyncPredictor tracker{mode, historySize, minimumSamplesForPrediction,
-                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/,
-                                      callback};
-    uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
-    tracker.setDisplayModePtr(ftl::as_non_null(
-            mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(period))));
-    for (uint16_t i = 0; i < minimumSamplesForPrediction; ++i) {
-        if (!tracker.needsMoreSamples()) {
-            break;
-        }
-        tracker.addVsyncTimestamp(now += period);
-    }
-    tracker.nextAnticipatedVSyncTimeFrom(now);
-    tracker.resetModel();
-}
-
-void SchedulerFuzzer::fuzzOneShotTimer() {
-    FakeClock* clock = new FakeClock();
-    std::unique_ptr<scheduler::OneShotTimer> idleTimer = std::make_unique<scheduler::OneShotTimer>(
-            mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/,
-            (std::chrono::milliseconds)mFdp.ConsumeIntegral<uint8_t>() /*val*/,
-            [] {} /*resetCallback*/, [] {} /*timeoutCallback*/, std::unique_ptr<FakeClock>(clock));
-    idleTimer->start();
-    idleTimer->reset();
-    idleTimer->stop();
-}
-
-void SchedulerFuzzer::fuzzLayerHistory() {
-    TestableSurfaceFlinger flinger;
-    flinger.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>());
-    flinger.setupTimeStats(std::make_unique<android::mock::TimeStats>());
-    std::unique_ptr<android::renderengine::RenderEngine> renderEngine =
-            std::make_unique<android::renderengine::mock::RenderEngine>();
-    flinger.setupRenderEngine(std::move(renderEngine));
-    flinger.setupComposer(std::make_unique<android::Hwc2::mock::Composer>());
-
-    scheduler::TestableScheduler* scheduler = flinger.scheduler();
-
-    scheduler::LayerHistory& historyV1 = scheduler->mutableLayerHistory();
-    nsecs_t time1 = systemTime();
-    nsecs_t time2 = time1;
-    uint8_t historySize = mFdp.ConsumeIntegral<uint8_t>();
-
-    sp<FuzzImplLayer> layer1 = sp<FuzzImplLayer>::make(flinger.flinger());
-    sp<FuzzImplLayer> layer2 = sp<FuzzImplLayer>::make(flinger.flinger());
-
-    for (int i = 0; i < historySize; ++i) {
-        historyV1.record(layer1->getSequence(), layer1->getLayerProps(), time1, time1,
-                         scheduler::LayerHistory::LayerUpdateType::Buffer);
-        historyV1.record(layer2->getSequence(), layer2->getLayerProps(), time2, time2,
-                         scheduler::LayerHistory::LayerUpdateType::Buffer);
-        time1 += mFdp.PickValueInArray(kVsyncPeriods);
-        time2 += mFdp.PickValueInArray(kVsyncPeriods);
-    }
-    historyV1.summarize(*scheduler->refreshRateSelector(), time1);
-    historyV1.summarize(*scheduler->refreshRateSelector(), time2);
-
-    scheduler->createConnection(std::make_unique<android::mock::EventThread>());
-
-    scheduler::ConnectionHandle handle;
-    scheduler->createDisplayEventConnection(handle);
-    scheduler->setDuration(handle, (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
-                           (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
-
-    std::string result = mFdp.ConsumeRandomLengthString(kRandomStringLength);
-    utils::Dumper dumper(result);
-    scheduler->dump(dumper);
-}
-
-void SchedulerFuzzer::fuzzVSyncReactor() {
-    std::shared_ptr<FuzzImplVSyncTracker> vSyncTracker = std::make_shared<FuzzImplVSyncTracker>();
-    scheduler::VSyncReactor reactor(kDisplayId,
-                                    std::make_unique<ClockWrapper>(
-                                            std::make_shared<FuzzImplClock>()),
-                                    *vSyncTracker, mFdp.ConsumeIntegral<uint8_t>() /*pendingLimit*/,
-                                    false);
-
-    const auto mode = ftl::as_non_null(
-            mock::createDisplayMode(DisplayModeId(0),
-                                    Fps::fromPeriodNsecs(mFdp.ConsumeIntegral<nsecs_t>())));
-    reactor.onDisplayModeChanged(mode, mFdp.ConsumeBool());
-    bool periodFlushed = false; // Value does not matter, since this is an out
-                                // param from addHwVsyncTimestamp.
-    reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed);
-    reactor.addHwVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>() /*newPeriod*/, std::nullopt,
-                                &periodFlushed);
-
-    const auto fence = std::make_shared<FenceTime>(makeFakeFence());
-    vSyncTracker->addVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>());
-    FenceTime::Snapshot snap(mFdp.ConsumeIntegral<nsecs_t>());
-    fence->applyTrustedSnapshot(snap);
-    reactor.setIgnorePresentFences(mFdp.ConsumeBool());
-    reactor.addPresentFence(fence);
-    dump<scheduler::VSyncReactor>(&reactor, &mFdp);
-}
-
-void SchedulerFuzzer::fuzzVSyncModulator() {
-    enum {
-        SF_OFFSET_LATE,
-        APP_OFFSET_LATE,
-        SF_DURATION_LATE,
-        APP_DURATION_LATE,
-        SF_OFFSET_EARLY,
-        APP_OFFSET_EARLY,
-        SF_DURATION_EARLY,
-        APP_DURATION_EARLY,
-        SF_OFFSET_EARLY_GPU,
-        APP_OFFSET_EARLY_GPU,
-        SF_DURATION_EARLY_GPU,
-        APP_DURATION_EARLY_GPU,
-        HWC_MIN_WORK_DURATION,
-    };
-    using Schedule = scheduler::TransactionSchedule;
-    using nanos = std::chrono::nanoseconds;
-    using FuzzImplVsyncModulator = scheduler::FuzzImplVsyncModulator;
-    const scheduler::VsyncConfig early{SF_OFFSET_EARLY, APP_OFFSET_EARLY, nanos(SF_DURATION_LATE),
-                                       nanos(APP_DURATION_LATE)};
-    const scheduler::VsyncConfig earlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU,
-                                          nanos(SF_DURATION_EARLY), nanos(APP_DURATION_EARLY)};
-    const scheduler::VsyncConfig late{SF_OFFSET_LATE, APP_OFFSET_LATE, nanos(SF_DURATION_EARLY_GPU),
-                                      nanos(APP_DURATION_EARLY_GPU)};
-    const scheduler::VsyncConfigSet offsets = {early, earlyGpu, late, nanos(HWC_MIN_WORK_DURATION)};
-    sp<FuzzImplVsyncModulator> vSyncModulator =
-            sp<FuzzImplVsyncModulator>::make(offsets, scheduler::Now);
-    (void)vSyncModulator->setVsyncConfigSet(offsets);
-    (void)vSyncModulator->setTransactionSchedule(Schedule::Late);
-    const auto token = sp<BBinder>::make();
-    (void)vSyncModulator->setTransactionSchedule(Schedule::EarlyStart, token);
-    vSyncModulator->binderDied(token);
-}
-
-void SchedulerFuzzer::fuzzRefreshRateSelection() {
-    TestableSurfaceFlinger flinger;
-    flinger.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>());
-
-    sp<Client> client;
-    LayerCreationArgs args(flinger.flinger(), client,
-                           mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/,
-                           mFdp.ConsumeIntegral<uint16_t>() /*layerFlags*/, LayerMetadata());
-    sp<Layer> layer = sp<Layer>::make(args);
-
-    layer->setFrameRateSelectionPriority(mFdp.ConsumeIntegral<int16_t>());
-}
-
-void SchedulerFuzzer::fuzzRefreshRateSelector() {
-    using RefreshRateSelector = scheduler::RefreshRateSelector;
-    using LayerRequirement = RefreshRateSelector::LayerRequirement;
-    using RefreshRateStats = scheduler::RefreshRateStats;
-
-    const uint16_t minRefreshRate = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX >> 1);
-    const uint16_t maxRefreshRate =
-            mFdp.ConsumeIntegralInRange<uint16_t>(minRefreshRate + 1, UINT16_MAX);
-
-    const DisplayModeId modeId{mFdp.ConsumeIntegralInRange<uint8_t>(0, 10)};
-
-    DisplayModes displayModes;
-    for (uint16_t fps = minRefreshRate; fps < maxRefreshRate; ++fps) {
-        displayModes.try_emplace(modeId,
-                                 mock::createDisplayMode(modeId,
-                                                         Fps::fromValue(static_cast<float>(fps))));
-    }
-
-    RefreshRateSelector refreshRateSelector(displayModes, modeId);
-
-    const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false};
-    std::vector<LayerRequirement> layers = {{.weight = mFdp.ConsumeFloatingPoint<float>()}};
-
-    refreshRateSelector.getRankedFrameRates(layers, globalSignals);
-
-    layers[0].name = mFdp.ConsumeRandomLengthString(kRandomStringLength);
-    layers[0].ownerUid = mFdp.ConsumeIntegral<uint16_t>();
-    layers[0].desiredRefreshRate = Fps::fromValue(mFdp.ConsumeFloatingPoint<float>());
-    layers[0].vote = mFdp.PickValueInArray(kLayerVoteTypes.values);
-    auto frameRateOverrides =
-            refreshRateSelector.getFrameRateOverrides(layers,
-                                                      Fps::fromValue(
-                                                              mFdp.ConsumeFloatingPoint<float>()),
-                                                      globalSignals);
-
-    {
-        ftl::FakeGuard guard(kMainThreadContext);
-
-        refreshRateSelector.setPolicy(
-                RefreshRateSelector::
-                        DisplayManagerPolicy{modeId,
-                                             {Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
-                                              Fps::fromValue(mFdp.ConsumeFloatingPoint<float>())}});
-        refreshRateSelector.setPolicy(
-                RefreshRateSelector::OverridePolicy{modeId,
-                                                    {Fps::fromValue(
-                                                             mFdp.ConsumeFloatingPoint<float>()),
-                                                     Fps::fromValue(
-                                                             mFdp.ConsumeFloatingPoint<float>())}});
-        refreshRateSelector.setPolicy(RefreshRateSelector::NoOverridePolicy{});
-
-        refreshRateSelector.setActiveMode(modeId,
-                                          Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
-    }
-
-    RefreshRateSelector::isFractionalPairOrMultiple(Fps::fromValue(
-                                                            mFdp.ConsumeFloatingPoint<float>()),
-                                                    Fps::fromValue(
-                                                            mFdp.ConsumeFloatingPoint<float>()));
-    RefreshRateSelector::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
-                                             Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
-
-    android::mock::TimeStats timeStats;
-    RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
-                                      PowerMode::OFF);
-
-    const auto fpsOpt = displayModes.get(modeId).transform(
-            [](const DisplayModePtr& mode) { return mode->getVsyncRate(); });
-    refreshRateStats.setRefreshRate(*fpsOpt);
-
-    refreshRateStats.setPowerMode(mFdp.PickValueInArray(kPowerModes));
-}
-
-void SchedulerFuzzer::fuzzPresentLatencyTracker() {
-    scheduler::PresentLatencyTracker tracker;
-
-    int i = 5;
-    while (i-- > 0) {
-        tracker.trackPendingFrame(getFuzzedTimePoint(mFdp),
-                                  std::make_shared<FenceTime>(makeFakeFence()));
-    }
-}
-
-void SchedulerFuzzer::fuzzFrameTargeter() {
-    scheduler::FrameTargeter frameTargeter(kDisplayId, mFdp.ConsumeBool());
-
-    const struct VsyncSource final : scheduler::IVsyncSource {
-        explicit VsyncSource(FuzzedDataProvider& fuzzer) : fuzzer(fuzzer) {}
-        FuzzedDataProvider& fuzzer;
-
-        Period period() const { return getFuzzedDuration(fuzzer); }
-        TimePoint vsyncDeadlineAfter(TimePoint) const { return getFuzzedTimePoint(fuzzer); }
-        Period minFramePeriod() const { return period(); }
-    } vsyncSource{mFdp};
-
-    int i = 10;
-    while (i-- > 0) {
-        frameTargeter.beginFrame({.frameBeginTime = getFuzzedTimePoint(mFdp),
-                                  .vsyncId = getFuzzedVsyncId(mFdp),
-                                  .expectedVsyncTime = getFuzzedTimePoint(mFdp),
-                                  .sfWorkDuration = getFuzzedDuration(mFdp)},
-                                 vsyncSource);
-
-        frameTargeter.setPresentFence(makeFakeFence());
-
-        frameTargeter.endFrame(
-                {.compositionCoverage = mFdp.PickValueInArray(kCompositionCoverage.values)});
-    }
-}
-
-void SchedulerFuzzer::process() {
-    fuzzRefreshRateSelection();
-    fuzzRefreshRateSelector();
-    fuzzPresentLatencyTracker();
-    fuzzFrameTargeter();
-    fuzzVSyncModulator();
-    fuzzVSyncPredictor();
-    fuzzVSyncReactor();
-    fuzzLayerHistory();
-    fuzzEventThread();
-    fuzzVSyncDispatchTimerQueue();
-    fuzzOneShotTimer();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    SchedulerFuzzer schedulerFuzzer(data, size);
-    schedulerFuzzer.process();
-    return 0;
-}
-
-} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
deleted file mode 100644
index fa307e9..0000000
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ /dev/null
@@ -1,201 +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.
- *
- */
-
-/*
-    Reference for some of the classes and functions has been taken from unittests
-    present in frameworks/native/services/surfaceflinger/tests/unittests
-*/
-
-#pragma once
-
-#include <scheduler/TimeKeeper.h>
-
-#include "Clock.h"
-#include "Layer.h"
-#include "Scheduler/EventThread.h"
-#include "Scheduler/Scheduler.h"
-#include "Scheduler/VSyncTracker.h"
-#include "Scheduler/VsyncModulator.h"
-
-namespace android::fuzz {
-
-class FuzzImplClock : public android::scheduler::Clock {
-public:
-    nsecs_t now() const { return 1; }
-};
-
-class ClockWrapper : public android::scheduler::Clock {
-public:
-    ClockWrapper(std::shared_ptr<android::scheduler::Clock> const& clock) : mClock(clock) {}
-
-    nsecs_t now() const { return mClock->now(); }
-
-private:
-    std::shared_ptr<android::scheduler::Clock> const mClock;
-};
-
-} // namespace android::fuzz
-
-namespace android {
-
-using namespace std::chrono_literals;
-
-class FakeClock : public Clock {
-public:
-    virtual ~FakeClock() = default;
-    std::chrono::steady_clock::time_point now() const override { return mNow; }
-
-    void advanceTime(std::chrono::nanoseconds delta) { mNow += delta; }
-
-private:
-    std::chrono::steady_clock::time_point mNow;
-};
-
-class FuzzImplLayer : public Layer {
-public:
-    FuzzImplLayer(SurfaceFlinger* flinger, std::string name)
-          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {}
-    explicit FuzzImplLayer(SurfaceFlinger* flinger) : FuzzImplLayer(flinger, "FuzzLayer") {}
-
-    const char* getType() const override { return ""; }
-
-    bool isVisible() const override { return true; }
-
-    sp<Layer> createClone(uint32_t /* mirrorRootId */) override { return nullptr; }
-};
-
-class FuzzImplVSyncTracker : public scheduler::VSyncTracker {
-public:
-    FuzzImplVSyncTracker(nsecs_t period) { mPeriod = period; }
-
-    FuzzImplVSyncTracker() = default;
-
-    bool addVsyncTimestamp(nsecs_t /* timestamp */) override { return true; }
-
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */) const override { return 1; }
-
-    nsecs_t currentPeriod() const override { return 1; }
-    Period minFramePeriod() const override { return Period::fromNs(currentPeriod()); }
-
-    void resetModel() override {}
-
-    bool needsMoreSamples() const override { return true; }
-
-    bool isVSyncInPhase(nsecs_t /* timePoint */, Fps /* frameRate */) const override {
-        return true;
-    }
-
-    void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) override {}
-
-    nsecs_t nextVSyncTime(nsecs_t timePoint) const {
-        if (timePoint % mPeriod == 0) {
-            return timePoint;
-        }
-        return (timePoint - (timePoint % mPeriod) + mPeriod);
-    }
-
-    void setRenderRate(Fps) override {}
-
-    void onFrameBegin(TimePoint, TimePoint) override {}
-
-    void onFrameMissed(TimePoint) override {}
-
-    void dump(std::string& /* result */) const override {}
-
-protected:
-    nsecs_t mPeriod;
-};
-
-class FuzzImplVSyncDispatch : public scheduler::VSyncDispatch {
-public:
-    CallbackToken registerCallback(Callback /* callbackFn */,
-                                   std::string /* callbackName */) override {
-        return CallbackToken{};
-    }
-
-    void unregisterCallback(CallbackToken /* token */) override {}
-
-    scheduler::ScheduleResult schedule(CallbackToken /* token */,
-                                       ScheduleTiming /* scheduleTiming */) override {
-        return (scheduler::ScheduleResult)0;
-    }
-
-    scheduler::ScheduleResult update(CallbackToken /* token */,
-                                     ScheduleTiming /* scheduleTiming */) override {
-        return (scheduler::ScheduleResult)0;
-    }
-
-    scheduler::CancelResult cancel(CallbackToken /* token */) override {
-        return (scheduler::CancelResult)0;
-    }
-
-    void dump(std::string& /* result */) const override {}
-};
-
-} // namespace android
-
-namespace android::scheduler {
-
-class ControllableClock : public TimeKeeper {
-public:
-    nsecs_t now() const { return 1; };
-    void alarmAt(std::function<void()> /* callback */, nsecs_t /* time */) override {}
-    void alarmCancel() override {}
-    void dump(std::string& /* result */) const override {}
-
-    void alarmAtDefaultBehavior(std::function<void()> const& callback, nsecs_t time) {
-        mCallback = callback;
-        mNextCallbackTime = time;
-    }
-
-    nsecs_t fakeTime() const { return mCurrentTime; }
-
-    void advanceToNextCallback() {
-        mCurrentTime = mNextCallbackTime;
-        if (mCallback) {
-            mCallback();
-        }
-    }
-
-    void advanceBy(nsecs_t advancement) {
-        mCurrentTime += advancement;
-        if (mCurrentTime >= (mNextCallbackTime + mLag) && mCallback) {
-            mCallback();
-        }
-    };
-
-    void setLag(nsecs_t lag) { mLag = lag; }
-
-private:
-    std::function<void()> mCallback;
-    nsecs_t mNextCallbackTime = 0;
-    nsecs_t mCurrentTime = 0;
-    nsecs_t mLag = 0;
-};
-
-static VsyncModulator::TimePoint Now() {
-    static VsyncModulator::TimePoint now;
-    return now += VsyncModulator::MIN_EARLY_TRANSACTION_TIME;
-}
-
-class FuzzImplVsyncModulator : public VsyncModulator {
-public:
-    FuzzImplVsyncModulator(const VsyncConfigSet& config, Now now) : VsyncModulator(config, now) {}
-
-    void binderDied(const wp<IBinder>& token) { VsyncModulator::binderDied(token); }
-};
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/layerproto/Android.bp b/services/surfaceflinger/layerproto/Android.bp
index a4dc8a0..f77b137 100644
--- a/services/surfaceflinger/layerproto/Android.bp
+++ b/services/surfaceflinger/layerproto/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_library {
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index abe7ec7..dea74d0 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -1,3 +1,5 @@
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 package: "com.android.graphics.surfaceflinger.flags"
 container: "system"
 
@@ -17,13 +19,6 @@
   is_fixed_read_only: true
 }
 
-flag{
-  name: "late_boot_misc2"
-  namespace: "core_graphics"
-  description: "This flag controls minor miscellaneous SurfaceFlinger changes. Cannot be read before boot finished!"
-  bug: "297389311"
-}
-
 flag {
   name: "vrr_config"
   namespace: "core_graphics"
@@ -32,6 +27,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "enable_layer_command_batching"
   namespace: "core_graphics"
@@ -41,13 +38,6 @@
 }
 
 flag {
-  name: "dont_skip_on_early"
-  namespace: "core_graphics"
-  description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
-  bug: "273702768"
-}
-
-flag {
   name: "multithreaded_present"
   namespace: "core_graphics"
   description: "Controls whether to offload present calls to another thread"
@@ -63,6 +53,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "hotplug2"
   namespace: "core_graphics"
@@ -87,6 +79,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "refresh_rate_overlay_on_external_display"
   namespace: "core_graphics"
@@ -99,10 +93,23 @@
   namespace: "core_graphics"
   description: "Whether to use the closest known refresh rate to determine the fps consistency."
   bug: "299201319"
+  is_fixed_read_only: true
 }
 
+# This flag is broken.
+# See alternative one: cache_when_source_crop_layer_only_moved
+# flag {
+#   name: "cache_if_source_crop_layer_only_moved"
+#   namespace: "core_graphics"
+#   description: "do not flatten layers if source crop is only moved"
+#   bug: "305718400"
+#   is_fixed_read_only: true
+# }
+
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
-  name: "cache_if_source_crop_layer_only_moved"
+  name: "cache_when_source_crop_layer_only_moved"
   namespace: "core_graphics"
   description: "do not flatten layers if source crop is only moved"
   bug: "305718400"
@@ -133,6 +140,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "game_default_frame_rate"
   namespace: "game"
@@ -140,3 +149,88 @@
   bug: "286084594"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "vulkan_renderengine"
+  namespace: "core_graphics"
+  description: "Use Vulkan backend in RenderEngine prior to switching to Graphite."
+  bug: "293371537"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "graphite_renderengine"
+  namespace: "core_graphics"
+  description: "Use Skia's Graphite Vulkan backend in RenderEngine."
+  bug: "293371537"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "screenshot_fence_preservation"
+  namespace: "core_graphics"
+  description: "Bug fix around screenshot fences"
+  bug: "302703346"
+  is_fixed_read_only: true
+}
+
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
+flag {
+  name: "renderable_buffer_usage"
+  namespace: "core_graphics"
+  description: "Decide whether an ExternalTexture isRenderable based on its buffer's usage."
+  bug: "305445199"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "restore_blur_step"
+  namespace: "core_graphics"
+  description: "Restore drawing the blur input prior to drawing blurred content."
+  bug: "255921628"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
+flag {
+  name: "dont_skip_on_early_ro"
+  namespace: "core_graphics"
+  description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
+  bug: "273702768"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "protected_if_client"
+  namespace: "core_graphics"
+  description: "Only set the RenderSurface to protected if protected layers are in client composition."
+  bug: "307674749"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "allow_n_vsyncs_in_targeter"
+  namespace: "core_graphics"
+  description: "This flag will enable utilizing N vsyncs in the FrameTargeter for past vsyncs"
+  bug: "308858993"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
new file mode 100644
index 0000000..02d8819
--- /dev/null
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -0,0 +1,109 @@
+# IMPORTANT - please keep alphabetize to reduce merge conflicts
+
+package: "com.android.graphics.surfaceflinger.flags"
+container: "system"
+
+flag {
+    name: "adpf_gpu_sf"
+    namespace: "game"
+    description: "Guards use of the sending ADPF GPU duration hint and load hints from SurfaceFlinger to Power HAL"
+    bug: "284324521"
+} # adpf_gpu_sf
+
+flag {
+  name: "ce_fence_promise"
+  namespace: "window_surfaces"
+  description: "Moves logic for buffer release fences into LayerFE"
+  bug: "294936197"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+ } # ce_fence_promise
+
+ flag {
+   name: "commit_not_composited"
+   namespace: "core_graphics"
+   description: "mark frames as non janky if the transaction resulted in no composition"
+   bug: "340633280"
+   is_fixed_read_only: true
+   metadata {
+     purpose: PURPOSE_BUGFIX
+   }
+  } # commit_not_composited
+
+ flag {
+  name: "deprecate_vsync_sf"
+  namespace: "core_graphics"
+  description: "Depracate eVsyncSourceSurfaceFlinger and use vsync_app everywhere"
+  bug: "162235855"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # deprecate_vsync_sf
+
+ flag {
+  name: "detached_mirror"
+  namespace: "window_surfaces"
+  description: "Ignore local transform when mirroring a partial hierarchy"
+  bug: "337845753"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # detached_mirror
+
+flag {
+  name: "frame_rate_category_mrr"
+  namespace: "core_graphics"
+  description: "Enable to use frame rate category and newer frame rate votes such as GTE in SurfaceFlinger scheduler, to guard dVRR changes from MRR devices"
+  bug: "330224639"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # frame_rate_category_mrr
+
+flag {
+  name: "latch_unsignaled_with_auto_refresh_changed"
+  namespace: "core_graphics"
+  description: "Ignore eAutoRefreshChanged with latch unsignaled"
+  bug: "331513837"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # latch_unsignaled_with_auto_refresh_changed
+
+flag {
+    name: "local_tonemap_screenshots"
+    namespace: "core_graphics"
+    description: "Enables local tonemapping when capturing screenshots"
+    bug: "329464641"
+    is_fixed_read_only: true
+} # local_tonemap_screenshots
+
+ flag {
+  name: "override_trusted_overlay"
+  namespace: "window_surfaces"
+  description: "Allow child to disable trusted overlay set by a parent layer"
+  bug: "339701674"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # override_trusted_overlay
+
+flag {
+  name: "vrr_bugfix_24q4"
+  namespace: "core_graphics"
+  description: "bug fixes for VRR"
+  bug: "331513837"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # vrr_bugfix_24q4
+
+# IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/sysprop/Android.bp b/services/surfaceflinger/sysprop/Android.bp
index f579119..4ea00cc 100644
--- a/services/surfaceflinger/sysprop/Android.bp
+++ b/services/surfaceflinger/sysprop/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 sysprop_library {
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 5449aeb..38fc977 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
@@ -26,6 +27,7 @@
     defaults: [
         "android.hardware.graphics.common-ndk_shared",
         "surfaceflinger_defaults",
+        "libsurfaceflinger_common_test_deps",
     ],
     test_suites: ["device-tests"],
     srcs: [
@@ -36,10 +38,10 @@
         "DereferenceSurfaceControl_test.cpp",
         "DisplayConfigs_test.cpp",
         "DisplayEventReceiver_test.cpp",
+        "Dumpsys_test.cpp",
         "EffectLayer_test.cpp",
         "HdrSdrRatioOverlay_test.cpp",
         "InvalidHandles_test.cpp",
-        "LayerBorder_test.cpp",
         "LayerCallback_test.cpp",
         "LayerRenderTypeTransaction_test.cpp",
         "LayerState_test.cpp",
@@ -65,7 +67,7 @@
     static_libs: [
         "liblayers_proto",
         "android.hardware.graphics.composer@2.1",
-        "libsurfaceflingerflags",
+        "libsurfaceflinger_common",
     ],
     shared_libs: [
         "android.hardware.graphics.common@1.2",
@@ -81,6 +83,7 @@
         "libprotobuf-cpp-full",
         "libui",
         "libutils",
+        "server_configurable_flags",
     ],
     header_libs: [
         "libnativewindow_headers",
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 822ac4d..ebe11fb 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -21,7 +21,6 @@
 #include <android/gui/ISurfaceComposer.h>
 #include <gtest/gtest.h>
 #include <gui/AidlStatusUtil.h>
-#include <gui/LayerDebugInfo.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <private/android_filesystem_config.h>
@@ -36,13 +35,12 @@
 namespace android {
 
 using Transaction = SurfaceComposerClient::Transaction;
-using gui::LayerDebugInfo;
 using gui::aidl_utils::statusTFromBinderStatus;
 using ui::ColorMode;
 
 namespace {
-const String8 DISPLAY_NAME("Credentials Display Test");
-const String8 SURFACE_NAME("Test Surface Name");
+const std::string kDisplayName("Credentials Display Test");
+const String8 kSurfaceName("Test Surface Name");
 } // namespace
 
 /**
@@ -101,7 +99,7 @@
         ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplay, &mode));
 
         // Background surface
-        mBGSurfaceControl = mComposerClient->createSurface(SURFACE_NAME, mode.resolution.getWidth(),
+        mBGSurfaceControl = mComposerClient->createSurface(kSurfaceName, mode.resolution.getWidth(),
                                                            mode.resolution.getHeight(),
                                                            PIXEL_FORMAT_RGBA_8888, 0);
         ASSERT_TRUE(mBGSurfaceControl != nullptr);
@@ -234,14 +232,14 @@
 TEST_F(CredentialsTest, CreateDisplayTest) {
     // Only graphics and system processes can create a secure display.
     std::function<bool()> condition = [=]() {
-        sp<IBinder> testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, true);
+        sp<IBinder> testDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName, true);
         return testDisplay.get() != nullptr;
     };
 
     // Check with root.
     {
         UIDFaker f(AID_ROOT);
-        ASSERT_FALSE(condition());
+        ASSERT_TRUE(condition());
     }
 
     // Check as a Graphics user.
@@ -269,7 +267,7 @@
     }
 
     condition = [=]() {
-        sp<IBinder> testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false);
+        sp<IBinder> testDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName, false);
         return testDisplay.get() != nullptr;
     };
     ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false));
@@ -292,35 +290,6 @@
 /**
  * The following tests are for methods accessible directly through SurfaceFlinger.
  */
-TEST_F(CredentialsTest, GetLayerDebugInfo) {
-    setupBackgroundSurface();
-    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
-
-    // Historically, only root and shell can access the getLayerDebugInfo which
-    // is called when we call dumpsys. I don't see a reason why we should change this.
-    std::vector<LayerDebugInfo> outLayers;
-    binder::Status status = binder::Status::ok();
-    // Check with root.
-    {
-        UIDFaker f(AID_ROOT);
-        status = sf->getLayerDebugInfo(&outLayers);
-        ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
-    }
-
-    // Check as a shell.
-    {
-        UIDFaker f(AID_SHELL);
-        status = sf->getLayerDebugInfo(&outLayers);
-        ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
-    }
-
-    // Check as anyone else.
-    {
-        UIDFaker f(AID_BIN);
-        status = sf->getLayerDebugInfo(&outLayers);
-        ASSERT_EQ(PERMISSION_DENIED, statusTFromBinderStatus(status));
-    }
-}
 
 TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) {
     const auto display = getFirstDisplayToken();
@@ -389,8 +358,13 @@
                 .apply();
     }
 
-    // Called from non privileged process
-    Transaction().setTrustedOverlay(surfaceControl, true);
+    // Attempt to set a trusted overlay from a non-privileged process. This should fail silently.
+    {
+        UIDFaker f{AID_BIN};
+        Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true);
+    }
+
+    // Verify that the layer was not made a trusted overlay.
     {
         UIDFaker f(AID_SYSTEM);
         auto windowIsPresentAndNotTrusted = [&](const std::vector<WindowInfo>& windowInfos) {
@@ -401,12 +375,14 @@
             }
             return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY);
         };
-        windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted);
+        ASSERT_TRUE(
+                windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted));
     }
 
+    // Verify that privileged processes are able to set trusted overlays.
     {
         UIDFaker f(AID_SYSTEM);
-        Transaction().setTrustedOverlay(surfaceControl, true);
+        Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true);
         auto windowIsPresentAndTrusted = [&](const std::vector<WindowInfo>& windowInfos) {
             auto foundWindowInfo =
                     WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos);
@@ -415,7 +391,8 @@
             }
             return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY);
         };
-        windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted);
+        ASSERT_TRUE(
+                windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted));
     }
 }
 
diff --git a/services/surfaceflinger/tests/Dumpsys_test.cpp b/services/surfaceflinger/tests/Dumpsys_test.cpp
new file mode 100644
index 0000000..c3914e5
--- /dev/null
+++ b/services/surfaceflinger/tests/Dumpsys_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/native_window.h>
+#include <gtest/gtest.h>
+#include <gui/SurfaceComposerClient.h>
+#include "android-base/stringprintf.h"
+#include "utils/Errors.h"
+
+namespace android {
+
+namespace {
+status_t runShellCommand(const std::string& cmd, std::string& result) {
+    FILE* pipe = popen(cmd.c_str(), "r");
+    if (!pipe) {
+        return UNKNOWN_ERROR;
+    }
+
+    char buffer[1024];
+    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
+        result += buffer;
+    }
+
+    pclose(pipe);
+    return OK;
+}
+} // namespace
+
+using android::hardware::graphics::common::V1_1::BufferUsage;
+
+class WaitForCompletedCallback {
+public:
+    WaitForCompletedCallback() = default;
+    ~WaitForCompletedCallback() = default;
+
+    static void transactionCompletedCallback(void* callbackContext, nsecs_t /* latchTime */,
+                                             const sp<Fence>& /* presentFence */,
+                                             const std::vector<SurfaceControlStats>& /* stats */) {
+        ASSERT_NE(callbackContext, nullptr) << "failed to get callback context";
+        WaitForCompletedCallback* context = static_cast<WaitForCompletedCallback*>(callbackContext);
+        context->notify();
+    }
+
+    void wait() {
+        std::unique_lock lock(mMutex);
+        cv.wait(lock, [this] { return mCallbackReceived; });
+    }
+
+    void notify() {
+        std::unique_lock lock(mMutex);
+        mCallbackReceived = true;
+        cv.notify_one();
+    }
+
+private:
+    std::mutex mMutex;
+    std::condition_variable cv;
+    bool mCallbackReceived = false;
+};
+
+TEST(Dumpsys, listLayers) {
+    sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+    ASSERT_EQ(NO_ERROR, client->initCheck());
+    auto newLayer =
+            client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0);
+    std::string layersAsString;
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --list", layersAsString));
+    EXPECT_NE(strstr(layersAsString.c_str(), ""), nullptr);
+}
+
+TEST(Dumpsys, stats) {
+    sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+    ASSERT_EQ(NO_ERROR, client->initCheck());
+    auto newLayer =
+            client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0);
+    uint64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+            BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
+
+    sp<GraphicBuffer> buffer =
+            sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, 1u, usageFlags, "test");
+
+    WaitForCompletedCallback callback;
+    SurfaceComposerClient::Transaction()
+            .setBuffer(newLayer, buffer)
+            .addTransactionCompletedCallback(WaitForCompletedCallback::transactionCompletedCallback,
+                                             &callback)
+            .apply();
+    callback.wait();
+    std::string stats;
+    std::string layerName = base::StringPrintf("MY_TEST_LAYER#%d", newLayer->getLayerId());
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency " + layerName, stats));
+    EXPECT_NE(std::string(""), stats);
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency-clear " + layerName, stats));
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp
deleted file mode 100644
index 00e134b..0000000
--- a/services/surfaceflinger/tests/LayerBorder_test.cpp
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-// TODO: Amend all tests when screenshots become fully reworked for borders
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
-#include <chrono> // std::chrono::seconds
-#include <thread> // std::this_thread::sleep_for
-#include "LayerTransactionTest.h"
-
-namespace android {
-
-class LayerBorderTest : public LayerTransactionTest {
-protected:
-    virtual void SetUp() {
-        LayerTransactionTest::SetUp();
-        ASSERT_EQ(NO_ERROR, mClient->initCheck());
-
-        toHalf3 = ColorTransformHelper::toHalf3;
-        toHalf4 = ColorTransformHelper::toHalf4;
-
-        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-        ASSERT_FALSE(ids.empty());
-        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-        ASSERT_FALSE(display == nullptr);
-        mColorOrange = toHalf4({255, 140, 0, 255});
-        mParentLayer = createColorLayer("Parent layer", Color::RED);
-
-        mContainerLayer = mClient->createSurface(String8("Container Layer"), 0 /* width */,
-                                                 0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                                 ISurfaceComposerClient::eFXSurfaceContainer |
-                                                         ISurfaceComposerClient::eNoColorFill,
-                                                 mParentLayer->getHandle());
-        EXPECT_NE(nullptr, mContainerLayer.get()) << "failed to create container layer";
-
-        mEffectLayer1 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
-                                               0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                               ISurfaceComposerClient::eFXSurfaceEffect |
-                                                       ISurfaceComposerClient::eNoColorFill,
-                                               mContainerLayer->getHandle());
-        EXPECT_NE(nullptr, mEffectLayer1.get()) << "failed to create effect layer 1";
-
-        mEffectLayer2 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
-                                               0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                               ISurfaceComposerClient::eFXSurfaceEffect |
-                                                       ISurfaceComposerClient::eNoColorFill,
-                                               mContainerLayer->getHandle());
-
-        EXPECT_NE(nullptr, mEffectLayer2.get()) << "failed to create effect layer 2";
-
-        asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-            t.setLayer(mParentLayer, INT32_MAX - 20).show(mParentLayer);
-            t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
-
-            t.setColor(mEffectLayer1, toHalf3(Color::BLUE));
-
-            t.setColor(mEffectLayer2, toHalf3(Color::GREEN));
-        });
-    }
-
-    virtual void TearDown() {
-        // Uncomment the line right below when running any of the tests
-        // std::this_thread::sleep_for (std::chrono::seconds(30));
-        LayerTransactionTest::TearDown();
-        mParentLayer = 0;
-    }
-
-    std::function<half3(Color)> toHalf3;
-    std::function<half4(Color)> toHalf4;
-    sp<SurfaceControl> mParentLayer, mContainerLayer, mEffectLayer1, mEffectLayer2;
-    half4 mColorOrange;
-};
-
-TEST_F(LayerBorderTest, OverlappingVisibleRegions) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, PartiallyCoveredVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, NonOverlappingVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
-        t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, EmptyVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(200, 200, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(0, 0, 600, 600));
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, ZOrderAdjustment) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setLayer(mParentLayer, 10);
-        t.setLayer(mEffectLayer1, 30);
-        t.setLayer(mEffectLayer2, 20);
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, GrandChildHierarchy) {
-    sp<SurfaceControl> containerLayer2 =
-            mClient->createSurface(String8("Container Layer"), 0 /* width */, 0 /* height */,
-                                   PIXEL_FORMAT_RGBA_8888,
-                                   ISurfaceComposerClient::eFXSurfaceContainer |
-                                           ISurfaceComposerClient::eNoColorFill,
-                                   mContainerLayer->getHandle());
-    EXPECT_NE(nullptr, containerLayer2.get()) << "failed to create container layer 2";
-
-    sp<SurfaceControl> effectLayer3 =
-            mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */,
-                                   PIXEL_FORMAT_RGBA_8888,
-                                   ISurfaceComposerClient::eFXSurfaceEffect |
-                                           ISurfaceComposerClient::eNoColorFill,
-                                   containerLayer2->getHandle());
-
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setCrop(effectLayer3, Rect(400, 400, 800, 800));
-        t.setColor(effectLayer3, toHalf3(Color::BLUE));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(effectLayer3);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, TransparentAlpha) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setAlpha(mEffectLayer1, 0.0f);
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, SemiTransparentAlpha) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setAlpha(mEffectLayer2, 0.5f);
-
-        t.enableBorder(mEffectLayer2, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, InvisibleLayers) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.hide(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, LayerWithBuffer) {
-    asTransaction([&](Transaction& t) {
-        t.hide(mEffectLayer1);
-        t.hide(mEffectLayer2);
-        t.show(mContainerLayer);
-
-        sp<SurfaceControl> layer =
-                mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */,
-                                       PIXEL_FORMAT_RGBA_8888,
-                                       ISurfaceComposerClient::eFXSurfaceBufferState,
-                                       mContainerLayer->getHandle());
-
-        sp<GraphicBuffer> buffer =
-                sp<GraphicBuffer>::make(400u, 400u, PIXEL_FORMAT_RGBA_8888, 1u,
-                                        BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
-                                                BufferUsage::COMPOSER_OVERLAY |
-                                                BufferUsage::GPU_TEXTURE,
-                                        "test");
-        TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 200, 200), Color::GREEN);
-        TransactionUtils::fillGraphicBufferColor(buffer, Rect(200, 200, 400, 400), Color::BLUE);
-
-        t.setBuffer(layer, buffer);
-        t.setPosition(layer, 100, 100);
-        t.show(layer);
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomWidth) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 50, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomColor) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, toHalf4({255, 0, 255, 255}));
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomWidthAndColorAndOpacity) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
-        t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 40, toHalf4({255, 255, 0, 128}));
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-} // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index 79886bd..b4496d3 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -1295,4 +1295,74 @@
     }
 }
 
+TEST_F(LayerCallbackTest, OccludedLayerHasReleaseCallback) {
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer());
+    ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer());
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1a, callback1b, callback2a, callback2b;
+    int err = fillTransaction(transaction1, &callback1a, layer1);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    err = fillTransaction(transaction2, &callback2a, layer2);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+
+    ui::Size bufferSize = getBufferSize();
+
+    // Occlude layer1 with layer2
+    TransactionUtils::setFrame(transaction1, layer1,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    TransactionUtils::setFrame(transaction2, layer2,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    transaction1.apply();
+    transaction2.apply();
+
+    ExpectedResult expected1a, expected1b, expected2a, expected2b;
+    expected1a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+    expected2a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1a, expected1a, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2a, expected2a, true));
+
+    // Submit new buffers so previous buffers can be released
+    err = fillTransaction(transaction1, &callback1b, layer1);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    err = fillTransaction(transaction2, &callback2b, layer2);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+
+    TransactionUtils::setFrame(transaction1, layer1,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    TransactionUtils::setFrame(transaction2, layer2,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    transaction1.apply();
+    transaction2.apply();
+
+    expected1b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::RELEASED);
+
+    expected2b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::RELEASED);
+
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1b, expected1b, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2b, expected2b, true));
+}
 } // namespace android
diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
index 2b1834d..4b3ad8a 100644
--- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
@@ -154,8 +154,6 @@
 
     switch (layerType) {
         case ISurfaceComposerClient::eFXSurfaceBufferQueue:
-            Transaction().setPosition(layerG, 16, 16).setRelativeLayer(layerG, layerR, 1).apply();
-            break;
         case ISurfaceComposerClient::eFXSurfaceBufferState:
             Transaction().setPosition(layerG, 16, 16).setRelativeLayer(layerG, layerR, 1).apply();
             break;
@@ -200,13 +198,6 @@
     // layerR = 0, layerG = layerR + 3, layerB = 2
     switch (layerType) {
         case ISurfaceComposerClient::eFXSurfaceBufferQueue:
-            Transaction()
-                    .setPosition(layerG, 8, 8)
-                    .setRelativeLayer(layerG, layerR, 3)
-                    .setPosition(layerB, 16, 16)
-                    .setLayer(layerB, mLayerZBase + 2)
-                    .apply();
-            break;
         case ISurfaceComposerClient::eFXSurfaceBufferState:
             Transaction()
                     .setPosition(layerG, 8, 8)
@@ -413,13 +404,6 @@
 
     switch (layerType) {
         case ISurfaceComposerClient::eFXSurfaceBufferQueue:
-            Transaction()
-                    .setAlpha(layer1, 0.25f)
-                    .setAlpha(layer2, 0.75f)
-                    .setPosition(layer2, 16, 0)
-                    .setLayer(layer2, mLayerZBase + 1)
-                    .apply();
-            break;
         case ISurfaceComposerClient::eFXSurfaceBufferState:
             Transaction()
                     .setAlpha(layer1, 0.25f)
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index c9af432..5b056d0 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -106,6 +106,10 @@
         return colorLayer;
     }
 
+    sp<SurfaceControl> mirrorSurface(SurfaceControl* mirrorFromSurface) {
+        return mClient->mirrorSurface(mirrorFromSurface);
+    }
+
     ANativeWindow_Buffer getBufferQueueLayerBuffer(const sp<SurfaceControl>& layer) {
         // wait for previous transactions (such as setSize) to complete
         Transaction().apply(true);
diff --git a/services/surfaceflinger/tests/LayerTransaction_test.cpp b/services/surfaceflinger/tests/LayerTransaction_test.cpp
index 03de8d0..ea141f3 100644
--- a/services/surfaceflinger/tests/LayerTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTransaction_test.cpp
@@ -213,6 +213,15 @@
     ASSERT_EQ(callCount, 1);
 }
 
+TEST_F(LayerTransactionTest, AddRemoveLayers) {
+    for (int i = 0; i < 100; i++) {
+        sp<SurfaceControl> layer;
+        ASSERT_NO_FATAL_FAILURE(
+                layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState));
+        layer.clear();
+    }
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp
index 0ea0824..d97d433 100644
--- a/services/surfaceflinger/tests/MirrorLayer_test.cpp
+++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp
@@ -19,6 +19,7 @@
 #pragma clang diagnostic ignored "-Wconversion"
 
 #include <android-base/properties.h>
+#include <common/FlagManager.h>
 #include <private/android_filesystem_config.h>
 #include "LayerTransactionTest.h"
 #include "utils/TransactionUtils.h"
@@ -78,6 +79,10 @@
             .show(mirrorLayer)
             .apply();
 
+    if (FlagManager::getInstance().detached_mirror()) {
+        Transaction().setPosition(mirrorLayer, 550, 550).apply();
+    }
+
     {
         SCOPED_TRACE("Initial Mirror");
         auto shot = screenshot();
@@ -172,6 +177,9 @@
             .show(mirrorLayer)
             .apply();
 
+    if (FlagManager::getInstance().detached_mirror()) {
+        Transaction().setPosition(mirrorLayer, 550, 550).apply();
+    }
     {
         SCOPED_TRACE("Initial Mirror BufferQueueLayer");
         auto shot = screenshot();
@@ -263,6 +271,9 @@
             .setLayer(mirrorLayer, INT32_MAX - 1)
             .apply();
 
+    if (FlagManager::getInstance().detached_mirror()) {
+        Transaction().setPosition(mirrorLayer, 550, 550).apply();
+    }
     {
         SCOPED_TRACE("Offscreen Mirror");
         auto shot = screenshot();
@@ -313,8 +324,15 @@
         ASSERT_NE(mirrorLayer, nullptr);
     }
 
+    sp<SurfaceControl> mirrorParent =
+            createLayer("Grandchild layer", 50, 50, ISurfaceComposerClient::eFXSurfaceBufferState);
+
     // Show the mirror layer, but don't reparent to a layer on screen.
-    Transaction().show(mirrorLayer).apply();
+    Transaction().reparent(mirrorLayer, mirrorParent).show(mirrorLayer).apply();
+
+    if (FlagManager::getInstance().detached_mirror()) {
+        Transaction().setPosition(mirrorLayer, 50, 50).apply();
+    }
 
     {
         SCOPED_TRACE("Offscreen Mirror");
@@ -331,7 +349,7 @@
         SCOPED_TRACE("Capture Mirror");
         // Capture just the mirror layer and child.
         LayerCaptureArgs captureArgs;
-        captureArgs.layerHandle = mirrorLayer->getHandle();
+        captureArgs.layerHandle = mirrorParent->getHandle();
         captureArgs.sourceCrop = childBounds;
         std::unique_ptr<ScreenCapture> shot;
         ScreenCapture::captureLayers(&shot, captureArgs);
diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
index 15ff696..56cf13d 100644
--- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
+++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
@@ -18,9 +18,12 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
+#include <common/FlagManager.h>
 #include <ui/DisplayState.h>
 
 #include "LayerTransactionTest.h"
+#include "gui/SurfaceComposerClient.h"
+#include "ui/DisplayId.h"
 
 namespace android {
 
@@ -37,7 +40,8 @@
 
         const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
         ASSERT_FALSE(ids.empty());
-        mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+        mMainDisplayId = ids.front();
+        mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(mMainDisplayId);
         SurfaceComposerClient::getDisplayState(mMainDisplay, &mMainDisplayState);
         SurfaceComposerClient::getActiveDisplayMode(mMainDisplay, &mMainDisplayMode);
 
@@ -49,14 +53,15 @@
     }
 
     virtual void TearDown() {
-        SurfaceComposerClient::destroyDisplay(mVirtualDisplay);
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::destroyVirtualDisplay(mVirtualDisplay));
         LayerTransactionTest::TearDown();
         mColorLayer = 0;
     }
 
     void createDisplay(const ui::Size& layerStackSize, ui::LayerStack layerStack) {
+        static const std::string kDisplayName("VirtualDisplay");
         mVirtualDisplay =
-                SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/);
+                SurfaceComposerClient::createVirtualDisplay(kDisplayName, false /*isSecure*/);
         asTransaction([&](Transaction& t) {
             t.setDisplaySurface(mVirtualDisplay, mProducer);
             t.setDisplayLayerStack(mVirtualDisplay, layerStack);
@@ -85,6 +90,7 @@
     ui::DisplayState mMainDisplayState;
     ui::DisplayMode mMainDisplayMode;
     sp<IBinder> mMainDisplay;
+    PhysicalDisplayId mMainDisplayId;
     sp<IBinder> mVirtualDisplay;
     sp<IGraphicBufferProducer> mProducer;
     sp<SurfaceControl> mColorLayer;
@@ -119,6 +125,9 @@
     createDisplay(mMainDisplayState.layerStackSpaceRect, ui::DEFAULT_LAYER_STACK);
     createColorLayer(ui::DEFAULT_LAYER_STACK);
 
+    sp<SurfaceControl> mirrorSc =
+            SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId);
+
     asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); });
 
     // Verify color layer renders correctly on main display and it is mirrored on the
@@ -133,6 +142,37 @@
     sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
 }
 
+TEST_F(MultiDisplayLayerBoundsTest, RenderLayerWithPromisedFenceInMirroredVirtualDisplay) {
+    // Create a display and use a unique layerstack ID for mirrorDisplay() so
+    // the contents of the main display are mirrored on to the virtual display.
+
+    // A unique layerstack ID must be used because sharing the same layerFE
+    // with more than one display is unsupported. A unique layerstack ensures
+    // that a different layerFE is used between displays.
+    constexpr ui::LayerStack layerStack{77687666}; // ASCII for MDLB (MultiDisplayLayerBounds)
+    createDisplay(mMainDisplayState.layerStackSpaceRect, layerStack);
+    createColorLayer(ui::DEFAULT_LAYER_STACK);
+
+    sp<SurfaceControl> mirrorSc =
+            SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId);
+
+    asTransaction([&](Transaction& t) {
+        t.setPosition(mColorLayer, 10, 10);
+        t.setLayerStack(mirrorSc, layerStack);
+    });
+
+    // Verify color layer renders correctly on main display and it is mirrored on the
+    // virtual display.
+    std::unique_ptr<ScreenCapture> sc;
+    ScreenCapture::captureScreen(&sc, mMainDisplay);
+    sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+    sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+
+    ScreenCapture::captureScreen(&sc, mVirtualDisplay);
+    sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+    sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 18262f6..9a78550 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -1039,6 +1039,29 @@
     ASSERT_TRUE(mCapture->capturedHdrLayers());
 }
 
+TEST_F(ScreenCaptureTest, captureOffscreenNullSnapshot) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32,
+                                                ISurfaceComposerClient::eFXSurfaceBufferState,
+                                                mBGSurfaceControl.get()));
+
+    // A mirrored layer will not have a snapshot. Testing an offscreen mirrored layer
+    // ensures that the screenshot path handles cases where snapshots are null.
+    sp<SurfaceControl> mirroredLayer;
+    ASSERT_NO_FATAL_FAILURE(mirroredLayer = mirrorSurface(layer.get()));
+
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = mirroredLayer->getHandle();
+    captureArgs.sourceCrop = Rect(0, 0, 1, 1);
+
+    // Screenshot path should only use the children of the layer hierarchy so
+    // that it will not create a new snapshot. A snapshot would otherwise be
+    // created to pass on the properties of the parent, which is not needed
+    // for the purposes of this test since we explicitly want a null snapshot.
+    captureArgs.childrenOnly = true;
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
+}
+
 // In the following tests we verify successful skipping of a parent layer,
 // so we use the same verification logic and only change how we mutate
 // the parent layer to verify that various properties are ignored.
diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index 797a64c..af3cb9a 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -16,9 +16,11 @@
 #ifndef ANDROID_TRANSACTION_TEST_HARNESSES
 #define ANDROID_TRANSACTION_TEST_HARNESSES
 
+#include <common/FlagManager.h>
 #include <ui/DisplayState.h>
 
 #include "LayerTransactionTest.h"
+#include "ui/LayerStack.h"
 
 namespace android {
 
@@ -36,9 +38,10 @@
             case RenderPath::VIRTUAL_DISPLAY:
 
                 const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+                const PhysicalDisplayId displayId = ids.front();
                 const auto displayToken = ids.empty()
                         ? nullptr
-                        : SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+                        : SurfaceComposerClient::getPhysicalDisplayToken(displayId);
 
                 ui::DisplayState displayState;
                 SurfaceComposerClient::getDisplayState(displayToken, &displayState);
@@ -63,14 +66,25 @@
                 sp<BufferListener> listener = sp<BufferListener>::make(this);
                 itemConsumer->setFrameAvailableListener(listener);
 
-                vDisplay = SurfaceComposerClient::createDisplay(String8("VirtualDisplay"),
-                                                                false /*secure*/);
+                static const std::string kDisplayName("VirtualDisplay");
+                vDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName,
+                                                                       false /*isSecure*/);
+
+                constexpr ui::LayerStack layerStack{
+                        848472}; // ASCII for TTH (TransactionTestHarnesses)
+                sp<SurfaceControl> mirrorSc =
+                        SurfaceComposerClient::getDefault()->mirrorDisplay(displayId);
 
                 SurfaceComposerClient::Transaction t;
                 t.setDisplaySurface(vDisplay, producer);
-                t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
                 t.setDisplayProjection(vDisplay, displayState.orientation,
                                        Rect(displayState.layerStackSpaceRect), Rect(resolution));
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    t.setDisplayLayerStack(vDisplay, layerStack);
+                    t.setLayerStack(mirrorSc, layerStack);
+                } else {
+                    t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
+                }
                 t.apply();
                 SurfaceComposerClient::Transaction().apply(true);
 
@@ -85,7 +99,16 @@
                 constexpr bool kContainsHdr = false;
                 auto sc = std::make_unique<ScreenCapture>(item.mGraphicBuffer, kContainsHdr);
                 itemConsumer->releaseBuffer(item);
-                SurfaceComposerClient::destroyDisplay(vDisplay);
+
+                // Possible race condition with destroying virtual displays, in which
+                // CompositionEngine::present may attempt to be called on the same
+                // display multiple times. The layerStack is set to invalid here so
+                // that the display is ignored if that scenario occurs.
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    t.setLayerStack(mirrorSc, ui::INVALID_LAYER_STACK);
+                    t.apply(true);
+                }
+                SurfaceComposerClient::destroyVirtualDisplay(vDisplay);
                 return sc;
         }
     }
diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
index f31f582..cd66dd2 100644
--- a/services/surfaceflinger/tests/VirtualDisplay_test.cpp
+++ b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
@@ -41,14 +41,15 @@
 };
 
 TEST_F(VirtualDisplayTest, VirtualDisplayDestroyedSurfaceReuse) {
+    static const std::string kDisplayName("VirtualDisplay");
     sp<IBinder> virtualDisplay =
-            SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/);
+            SurfaceComposerClient::createVirtualDisplay(kDisplayName, false /*isSecure*/);
 
     SurfaceComposerClient::Transaction t;
     t.setDisplaySurface(virtualDisplay, mProducer);
     t.apply(true);
 
-    SurfaceComposerClient::destroyDisplay(virtualDisplay);
+    EXPECT_EQ(NO_ERROR, SurfaceComposerClient::destroyVirtualDisplay(virtualDisplay));
     virtualDisplay.clear();
     // Sync here to ensure the display was completely destroyed in SF
     t.apply(true);
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
index aeceadb..bce1406 100644
--- a/services/surfaceflinger/tests/tracing/Android.bp
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_test {
diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
index f1bb231..b17b529 100644
--- a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
@@ -17,7 +17,7 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
-#include "DisplayTransactionTestHelpers.h"
+#include "DualDisplayTransactionTest.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -25,31 +25,10 @@
 namespace android {
 namespace {
 
-struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest {
-    static constexpr bool kWithMockScheduler = false;
-    ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {}
-
+struct ActiveDisplayRotationFlagsTest
+      : DualDisplayTransactionTest<hal::PowerMode::ON, hal::PowerMode::OFF> {
     void SetUp() override {
-        injectMockScheduler(kInnerDisplayId);
-
-        // Inject inner and outer displays with uninitialized power modes.
-        constexpr bool kInitPowerMode = false;
-        {
-            InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
-            auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
-            injector.setPowerMode(std::nullopt);
-            injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
-            mInnerDisplay = injector.inject();
-        }
-        {
-            OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
-            auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
-            injector.setPowerMode(std::nullopt);
-            mOuterDisplay = injector.inject();
-        }
-
-        mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
-        mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+        DualDisplayTransactionTest::SetUp();
 
         // The flags are a static variable, so by modifying them in the test, we
         // are modifying the real ones used by SurfaceFlinger. Save the original
@@ -64,10 +43,6 @@
 
     void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; }
 
-    static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
-    static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
-
-    sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
     ui::Transform::RotationFlags mOldRotationFlags;
 };
 
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index c75f902..5145e11 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 filegroup {
@@ -28,7 +29,7 @@
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockHWC2.cpp",
         "mock/DisplayHardware/MockIPower.cpp",
-        "mock/DisplayHardware/MockIPowerHintSession.cpp",
+        "mock/DisplayHardware/MockPowerHintSessionWrapper.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
         "mock/MockEventThread.cpp",
         "mock/MockFrameTimeline.cpp",
@@ -65,6 +66,7 @@
         "BackgroundExecutorTest.cpp",
         "CommitTest.cpp",
         "CompositionTest.cpp",
+        "DaltonizerTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
         "DisplayDevice_GetBestColorModeTest.cpp",
@@ -95,6 +97,7 @@
         "MessageQueueTest.cpp",
         "PowerAdvisorTest.cpp",
         "SmallAreaDetectionAllowMappingsTest.cpp",
+        "SurfaceFlinger_ColorMatrixTest.cpp",
         "SurfaceFlinger_CreateDisplayTest.cpp",
         "SurfaceFlinger_DestroyDisplayTest.cpp",
         "SurfaceFlinger_DisplayModeSwitching.cpp",
@@ -118,6 +121,7 @@
         "RefreshRateSelectorTest.cpp",
         "RefreshRateStatsTest.cpp",
         "RegionSamplingTest.cpp",
+        "TestableScheduler.cpp",
         "TimeStatsTest.cpp",
         "FrameTracerTest.cpp",
         "TransactionApplicationTest.cpp",
@@ -127,7 +131,6 @@
         "TransactionTraceWriterTest.cpp",
         "TransactionTracingTest.cpp",
         "TunnelModeEnabledReporterTest.cpp",
-        "StrongTypingTest.cpp",
         "VSyncCallbackRegistrationTest.cpp",
         "VSyncDispatchTimerQueueTest.cpp",
         "VSyncDispatchRealtimeTest.cpp",
@@ -147,6 +150,7 @@
         "android.hardware.graphics.composer3-ndk_static",
         "android.hardware.power-ndk_static",
         "librenderengine_deps",
+        "libsurfaceflinger_common_test_deps",
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
@@ -171,13 +175,11 @@
         "librenderengine_mocks",
         "libscheduler",
         "libserviceutils",
-        "libsurfaceflinger_common_test",
         "libtimestats",
         "libtimestats_atoms_proto",
         "libtimestats_proto",
         "libtonemap",
         "perfetto_trace_protos",
-        "libsurfaceflingerflags_test",
     ],
     shared_libs: [
         "android.hardware.configstore-utils",
@@ -206,7 +208,6 @@
         "libsync",
         "libui",
         "libutils",
-        "server_configurable_flags",
     ],
     header_libs: [
         "android.hardware.graphics.composer3-command-buffer",
diff --git a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
new file mode 100644
index 0000000..d4c801f
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <compositionengine/Display.h>
+#include <compositionengine/mock/DisplaySurface.h>
+#include <renderengine/mock/RenderEngine.h>
+
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/MockTimeStats.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+namespace android {
+
+// Minimal setup to use TestableSurfaceFlinger::commitAndComposite.
+struct CommitAndCompositeTest : testing::Test {
+    void SetUp() override {
+        mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
+        mComposer = new Hwc2::mock::Composer();
+        mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
+        mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+        mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
+        mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+        mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
+
+        constexpr bool kIsPrimary = true;
+        FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
+                .setPowerMode(hal::PowerMode::ON)
+                .inject(&mFlinger, mComposer);
+        auto compostionEngineDisplayArgs =
+                compositionengine::DisplayCreationArgsBuilder()
+                        .setId(DEFAULT_DISPLAY_ID)
+                        .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
+                        .setPowerAdvisor(mPowerAdvisor)
+                        .setName("Internal display")
+                        .build();
+        auto compositionDisplay =
+                compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
+                                                       std::move(compostionEngineDisplayArgs));
+        mDisplay = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+                                             ui::DisplayConnectionType::Internal, HWC_DISPLAY,
+                                             kIsPrimary)
+                           .setDisplaySurface(mDisplaySurface)
+                           .setNativeWindow(mNativeWindow)
+                           .setPowerMode(hal::PowerMode::ON)
+                           .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
+                           .skipSchedulerRegistration()
+                           .inject();
+    }
+
+    using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+    using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+    static constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+    static constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+    static constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
+    static constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
+
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+    sp<DisplayDevice> mDisplay;
+    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
+            sp<compositionengine::mock::DisplaySurface>::make();
+    sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
+    mock::TimeStats* mTimeStats = new mock::TimeStats();
+    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
+    Hwc2::mock::Composer* mComposer = nullptr;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CommitTest.cpp b/services/surfaceflinger/tests/unittests/CommitTest.cpp
index df53d19..7f29418 100644
--- a/services/surfaceflinger/tests/unittests/CommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CommitTest.cpp
@@ -17,9 +17,17 @@
 #undef LOG_TAG
 #define LOG_TAG "CommitTest"
 
+#include <DisplayHardware/HWComposer.h>
+#include <FrontEnd/LayerCreationArgs.h>
+#include <FrontEnd/RequestedLayerState.h>
+#include <compositionengine/CompositionEngine.h>
+#include <compositionengine/Feature.h>
+#include <compositionengine/mock/CompositionEngine.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-
+#include <gui/LayerMetadata.h>
+#include <gui/SurfaceComposerClient.h>
+#include <mock/DisplayHardware/MockComposer.h>
 #include <renderengine/mock/RenderEngine.h>
 #include "TestableSurfaceFlinger.h"
 
@@ -27,18 +35,27 @@
 
 class CommitTest : public testing::Test {
 protected:
-    CommitTest() {
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+
+    void flinger_setup() {
         mFlinger.setupMockScheduler();
         mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
         mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
     }
-    TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+
+    LayerCreationArgs createArgs(uint32_t id, LayerMetadata metadata, uint32_t parentId) {
+        LayerCreationArgs args(mFlinger.flinger(), nullptr, "layer",
+                               gui::ISurfaceComposerClient::eNoColorFill, metadata, id);
+        args.parentId = parentId;
+        return args;
+    }
 };
 
 namespace {
 
 TEST_F(CommitTest, noUpdatesDoesNotScheduleComposite) {
+    flinger_setup();
     bool unused;
     bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
                                                        /*transactionsFlushed=*/0, unused);
@@ -47,6 +64,7 @@
 
 // Ensure that we handle eTransactionNeeded correctly
 TEST_F(CommitTest, eTransactionNeededFlagSchedulesComposite) {
+    flinger_setup();
     // update display level color matrix
     mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly);
     bool unused;
@@ -55,5 +73,92 @@
     EXPECT_TRUE(mustComposite);
 }
 
+TEST_F(CommitTest, metadataNotIncluded) {
+    mFlinger.setupMockScheduler();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    compositionengine::mock::CompositionEngine* mCompositionEngine =
+            new compositionengine::mock::CompositionEngine();
+
+    // CompositionEngine setup with unset flag
+    compositionengine::FeatureFlags flags;
+    impl::HWComposer hwc = impl::HWComposer(std::make_unique<Hwc2::mock::Composer>());
+
+    EXPECT_CALL(*mCompositionEngine, getFeatureFlags).WillOnce(testing::Return(flags));
+    EXPECT_THAT(flags.test(compositionengine::Feature::kSnapshotLayerMetadata), false);
+
+    EXPECT_CALL(*mCompositionEngine, getHwComposer).WillOnce(testing::ReturnRef(hwc));
+
+    mFlinger.setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine>(mCompositionEngine));
+
+    // Create a parent layer with metadata and a child layer without. Metadata should not
+    // be included in the child layer when the flag is not set.
+    std::unordered_map<uint32_t, std::vector<uint8_t>> metadata = {{1, {'a', 'b'}}};
+    auto parentArgs = createArgs(1, LayerMetadata(metadata), UNASSIGNED_LAYER_ID);
+    auto parent = std::make_unique<frontend::RequestedLayerState>(parentArgs);
+    mFlinger.addLayer(parent);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(parentArgs));
+
+    auto childArgs = createArgs(11, LayerMetadata(), 1);
+    auto child = std::make_unique<frontend::RequestedLayerState>(childArgs);
+    mFlinger.addLayer(child);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(childArgs));
+
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/1, unused);
+    EXPECT_TRUE(mustComposite);
+
+    auto parentMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->layerMetadata.mMap;
+    auto childMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(11)->layerMetadata.mMap;
+
+    EXPECT_EQ(metadata.at(1), parentMetadata.at(1));
+    EXPECT_NE(parentMetadata, childMetadata);
+}
+
+TEST_F(CommitTest, metadataIsIncluded) {
+    mFlinger.setupMockScheduler();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    compositionengine::mock::CompositionEngine* mCompositionEngine =
+            new compositionengine::mock::CompositionEngine();
+
+    // CompositionEngine setup with set flag
+    compositionengine::FeatureFlags flags;
+    flags |= compositionengine::Feature::kSnapshotLayerMetadata;
+    impl::HWComposer hwc = impl::HWComposer(std::make_unique<Hwc2::mock::Composer>());
+
+    EXPECT_CALL(*mCompositionEngine, getFeatureFlags).WillOnce(testing::Return(flags));
+    EXPECT_THAT(flags.test(compositionengine::Feature::kSnapshotLayerMetadata), true);
+
+    EXPECT_CALL(*mCompositionEngine, getHwComposer).WillOnce(testing::ReturnRef(hwc));
+
+    mFlinger.setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine>(mCompositionEngine));
+
+    // Create a parent layer with metadata and a child layer without. Metadata from the
+    // parent should be included in the child layer when the flag is set.
+    std::unordered_map<uint32_t, std::vector<uint8_t>> metadata = {{1, {'a', 'b'}}};
+    auto parentArgs = createArgs(1, LayerMetadata(metadata), UNASSIGNED_LAYER_ID);
+    auto parent = std::make_unique<frontend::RequestedLayerState>(parentArgs);
+    mFlinger.addLayer(parent);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(parentArgs));
+
+    auto childArgs = createArgs(11, LayerMetadata(), 1);
+    auto child = std::make_unique<frontend::RequestedLayerState>(childArgs);
+    mFlinger.addLayer(child);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(childArgs));
+
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/1, unused);
+    EXPECT_TRUE(mustComposite);
+
+    auto parentMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->layerMetadata.mMap;
+    auto childMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(11)->layerMetadata.mMap;
+
+    EXPECT_EQ(metadata.at(1), parentMetadata.at(1));
+    EXPECT_EQ(parentMetadata, childMetadata);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index beb2147..0ddddbd 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -25,6 +25,7 @@
 
 #include <compositionengine/Display.h>
 #include <compositionengine/mock/DisplaySurface.h>
+#include <ftl/future.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <gui/IProducerListener.h>
@@ -227,14 +228,6 @@
     LayerCase::cleanup(this);
 }
 
-template <class T>
-std::future<T> futureOf(T obj) {
-    std::promise<T> resultPromise;
-    std::future<T> resultFuture = resultPromise.get_future();
-    resultPromise.set_value(std::move(obj));
-    return resultFuture;
-}
-
 /* ------------------------------------------------------------------------
  * Variants for each display configuration which can be tested
  */
@@ -289,7 +282,7 @@
                         .setSecure(Derived::IS_SECURE)
                         .setPowerMode(Derived::INIT_POWER_MODE)
                         .setRefreshRateSelector(test->mFlinger.scheduler()->refreshRateSelector())
-                        .skipRegisterDisplay()
+                        .skipSchedulerRegistration()
                         .inject();
         Mock::VerifyAndClear(test->mNativeWindow.get());
 
@@ -327,13 +320,13 @@
                 .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
                                     const std::vector<renderengine::LayerSettings>&,
                                     const std::shared_ptr<renderengine::ExternalTexture>&,
-                                    base::unique_fd&&) -> std::future<FenceResult> {
+                                    base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.clip);
-                    return futureOf<FenceResult>(Fence::NO_FENCE);
+                    return ftl::yield<FenceResult>(Fence::NO_FENCE);
                 });
     }
 
@@ -378,14 +371,14 @@
                 .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
                                     const std::vector<renderengine::LayerSettings>&,
                                     const std::shared_ptr<renderengine::ExternalTexture>&,
-                                    base::unique_fd&&) -> std::future<FenceResult> {
+                                    base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.clip);
                     EXPECT_EQ(ui::Dataspace::UNKNOWN, displaySettings.outputDataspace);
-                    return futureOf<FenceResult>(Fence::NO_FENCE);
+                    return ftl::yield<FenceResult>(Fence::NO_FENCE);
                 });
     }
 
@@ -578,7 +571,7 @@
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
                               const std::shared_ptr<renderengine::ExternalTexture>&,
-                              base::unique_fd&&) -> std::future<FenceResult> {
+                              base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -586,7 +579,8 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so gtet the back layer.
-                    std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
+                    ftl::Future<FenceResult> resultFuture =
+                            ftl::yield<FenceResult>(Fence::NO_FENCE);
                     if (layerSettings.empty()) {
                         ADD_FAILURE() << "layerSettings was not expected to be empty in "
                                          "setupREBufferCompositionCommonCallExpectations "
@@ -627,7 +621,7 @@
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
                               const std::shared_ptr<renderengine::ExternalTexture>&,
-                              base::unique_fd&&) -> std::future<FenceResult> {
+                              base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -635,7 +629,8 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so get the back layer.
-                    std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
+                    ftl::Future<FenceResult> resultFuture =
+                            ftl::yield<FenceResult>(Fence::NO_FENCE);
                     if (layerSettings.empty()) {
                         ADD_FAILURE()
                                 << "layerSettings was not expected to be empty in "
@@ -709,7 +704,7 @@
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
                               const std::shared_ptr<renderengine::ExternalTexture>&,
-                              base::unique_fd&&) -> std::future<FenceResult> {
+                              base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -717,7 +712,8 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so get the back layer.
-                    std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
+                    ftl::Future<FenceResult> resultFuture =
+                            ftl::yield<FenceResult>(Fence::NO_FENCE);
                     if (layerSettings.empty()) {
                         ADD_FAILURE() << "layerSettings was not expected to be empty in "
                                          "setupInsecureREBufferCompositionCommonCallExpectations "
diff --git a/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp b/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp
new file mode 100644
index 0000000..9f632a1
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <math/mat4.h>
+#include <cmath>
+#include "Effects/Daltonizer.h"
+
+namespace android {
+
+class DaltonizerTest {
+private:
+    Daltonizer& mDaltonizer;
+
+public:
+    DaltonizerTest(Daltonizer& daltonizer) : mDaltonizer(daltonizer) {}
+
+    bool isDirty() const { return mDaltonizer.mDirty; }
+
+    float getLevel() const { return mDaltonizer.mLevel; }
+
+    ColorBlindnessType getType() const { return mDaltonizer.mType; }
+};
+
+constexpr float TOLERANCE = 0.01f;
+
+static bool isIdentityMatrix(mat4& matrix) {
+    for (size_t i = 0; i < 4; ++i) {
+        for (size_t j = 0; j < 4; ++j) {
+            if (i == j) {
+                // Check diagonal elements
+                if (std::fabs(matrix[i][j] - 1.0f) > TOLERANCE) {
+                    return false;
+                }
+            } else {
+                // Check off-diagonal elements
+                if (std::fabs(matrix[i][j]) > TOLERANCE) {
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+// Test Suite Name : DaltonizerTest, Test name: ConstructionDefaultValues
+TEST(DaltonizerTest, ConstructionDefaultValues) {
+    Daltonizer daltonizer;
+    DaltonizerTest test(daltonizer);
+
+    EXPECT_EQ(test.getLevel(), 0.7f);
+    ASSERT_TRUE(test.isDirty());
+    EXPECT_EQ(test.getType(), ColorBlindnessType::None);
+    mat4 matrix = daltonizer();
+    ASSERT_TRUE(isIdentityMatrix(matrix));
+}
+
+TEST(DaltonizerTest, NotDirtyAfterColorMatrixReturned) {
+    Daltonizer daltonizer;
+
+    mat4 matrix = daltonizer();
+    DaltonizerTest test(daltonizer);
+
+    ASSERT_FALSE(test.isDirty());
+    ASSERT_TRUE(isIdentityMatrix(matrix));
+}
+
+TEST(DaltonizerTest, LevelOutOfRangeTooLowIgnored) {
+    Daltonizer daltonizer;
+    // Get matrix to reset isDirty == false.
+    mat4 matrix = daltonizer();
+
+    daltonizer.setLevel(-1);
+    DaltonizerTest test(daltonizer);
+
+    EXPECT_EQ(test.getLevel(), 0.7f);
+    ASSERT_FALSE(test.isDirty());
+}
+
+TEST(DaltonizerTest, LevelOutOfRangeTooHighIgnored) {
+    Daltonizer daltonizer;
+    // Get matrix to reset isDirty == false.
+    mat4 matrix = daltonizer();
+
+    daltonizer.setLevel(11);
+    DaltonizerTest test(daltonizer);
+
+    EXPECT_EQ(test.getLevel(), 0.7f);
+    ASSERT_FALSE(test.isDirty());
+}
+
+TEST(DaltonizerTest, ColorCorrectionMatrixNonIdentical) {
+    Daltonizer daltonizer;
+    daltonizer.setType(ColorBlindnessType::Protanomaly);
+    daltonizer.setMode(ColorBlindnessMode::Correction);
+
+    mat4 matrix = daltonizer();
+
+    ASSERT_FALSE(isIdentityMatrix(matrix));
+}
+
+TEST(DaltonizerTest, LevelZeroColorMatrixEqIdentityMatrix) {
+    Daltonizer daltonizer;
+    daltonizer.setType(ColorBlindnessType::Protanomaly);
+    daltonizer.setMode(ColorBlindnessMode::Correction);
+    daltonizer.setLevel(0);
+
+    mat4 matrix = daltonizer();
+
+    ASSERT_TRUE(isIdentityMatrix(matrix));
+}
+
+} /* namespace android */
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 1379665..fa31643 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -80,8 +80,7 @@
                             std::unique_ptr<EventThread>(mEventThread),
                             std::unique_ptr<EventThread>(mSFEventThread),
                             TestableSurfaceFlinger::DefaultDisplayMode{displayId},
-                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock,
-                            TestableSurfaceFlinger::VsyncTrackerCallbackImpl::kMock);
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock);
 }
 
 void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) {
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index ee12276..f26336a 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -347,7 +347,6 @@
 
     // The HWC active configuration id
     static constexpr hal::HWConfigId HWC_ACTIVE_CONFIG_ID = 2001;
-    static constexpr PowerMode INIT_POWER_MODE = hal::PowerMode::ON;
 
     static void injectPendingHotplugEvent(DisplayTransactionTest* test, Connection connection) {
         test->mFlinger.mutablePendingHotplugEvents().emplace_back(
@@ -355,7 +354,7 @@
     }
 
     // Called by tests to inject a HWC display setup
-    template <bool kInitPowerMode = true>
+    template <hal::PowerMode kPowerMode = hal::PowerMode::ON>
     static void injectHwcDisplayWithNoDefaultCapabilities(DisplayTransactionTest* test) {
         const auto displayId = DisplayVariant::DISPLAY_ID::get();
         ASSERT_FALSE(GpuVirtualDisplayId::tryCast(displayId));
@@ -364,22 +363,37 @@
                 .setHwcDisplayId(HWC_DISPLAY_ID)
                 .setResolution(DisplayVariant::RESOLUTION)
                 .setActiveConfig(HWC_ACTIVE_CONFIG_ID)
-                .setPowerMode(kInitPowerMode ? std::make_optional(INIT_POWER_MODE) : std::nullopt)
+                .setPowerMode(kPowerMode)
                 .inject(&test->mFlinger, test->mComposer);
     }
 
     // Called by tests to inject a HWC display setup
-    template <bool kInitPowerMode = true>
+    //
+    // TODO(b/241285876): The `kExpectSetPowerModeOnce` argument is set to `false` by tests that
+    // power on/off displays several times. Replace those catch-all expectations with `InSequence`
+    // and `RetiresOnSaturation`.
+    //
+    template <hal::PowerMode kPowerMode = hal::PowerMode::ON, bool kExpectSetPowerModeOnce = true>
     static void injectHwcDisplay(DisplayTransactionTest* test) {
-        if constexpr (kInitPowerMode) {
-            EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
-                    .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
-                                    Return(Error::NONE)));
+        if constexpr (kExpectSetPowerModeOnce) {
+            if constexpr (kPowerMode == hal::PowerMode::ON) {
+                EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
+                        .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
+                                        Return(Error::NONE)));
+            }
 
-            EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE))
+            EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, kPowerMode))
                     .WillOnce(Return(Error::NONE));
+        } else {
+            EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
+                    .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
+                                          Return(Error::NONE)));
+
+            EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, _))
+                    .WillRepeatedly(Return(Error::NONE));
         }
-        injectHwcDisplayWithNoDefaultCapabilities<kInitPowerMode>(test);
+
+        injectHwcDisplayWithNoDefaultCapabilities<kPowerMode>(test);
     }
 
     static std::shared_ptr<compositionengine::Display> injectCompositionDisplay(
@@ -447,9 +461,11 @@
                     ? IComposerClient::DisplayConnectionType::INTERNAL
                     : IComposerClient::DisplayConnectionType::EXTERNAL;
 
+            using ::testing::AtLeast;
             EXPECT_CALL(*test->mComposer, getDisplayConnectionType(HWC_DISPLAY_ID, _))
-                    .WillOnce(DoAll(SetArgPointee<1>(CONNECTION_TYPE),
-                                    Return(hal::V2_4::Error::NONE)));
+                    .Times(AtLeast(1))
+                    .WillRepeatedly(DoAll(SetArgPointee<1>(CONNECTION_TYPE),
+                                          Return(hal::V2_4::Error::NONE)));
         }
 
         EXPECT_CALL(*test->mComposer, setClientTargetSlotCount(_))
@@ -481,14 +497,17 @@
 
 constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1;
 
-template <typename PhysicalDisplay, int width, int height>
+template <typename PhysicalDisplay, int width, int height,
+          Secure secure = (PhysicalDisplay::CONNECTION_TYPE == ui::DisplayConnectionType::Internal)
+                  ? Secure::TRUE
+                  : Secure::FALSE>
 struct PhysicalDisplayVariant
-      : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE,
-                       Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY,
+      : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE, secure,
+                       PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY,
                        PHYSICAL_DISPLAY_FLAGS>,
         HwcDisplayVariant<PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL,
                           DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height,
-                                         Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY,
+                                         Async::FALSE, secure, PhysicalDisplay::PRIMARY,
                                          GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>,
                           PhysicalDisplay> {};
 
@@ -515,6 +534,7 @@
 };
 
 struct TertiaryDisplay {
+    static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External;
     static constexpr Primary PRIMARY = Primary::FALSE;
     static constexpr uint8_t PORT = 253;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1003;
diff --git a/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h b/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h
new file mode 100644
index 0000000..90e716f
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+template <hal::PowerMode kInnerDisplayPowerMode, hal::PowerMode kOuterDisplayPowerMode,
+          bool kExpectSetPowerModeOnce = true>
+struct DualDisplayTransactionTest : DisplayTransactionTest {
+    static constexpr bool kWithMockScheduler = false;
+    DualDisplayTransactionTest() : DisplayTransactionTest(kWithMockScheduler) {}
+
+    void SetUp() override {
+        injectMockScheduler(kInnerDisplayId);
+
+        {
+            InnerDisplayVariant::injectHwcDisplay<kInnerDisplayPowerMode, kExpectSetPowerModeOnce>(
+                    this);
+
+            auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
+            injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
+            injector.setPowerMode(kInnerDisplayPowerMode);
+            mInnerDisplay = injector.inject();
+        }
+        {
+            OuterDisplayVariant::injectHwcDisplay<kOuterDisplayPowerMode, kExpectSetPowerModeOnce>(
+                    this);
+
+            auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
+            injector.setPowerMode(kOuterDisplayPowerMode);
+            mOuterDisplay = injector.inject();
+        }
+    }
+
+    static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
+    static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
+
+    sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 8891c06..625d2e6 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -55,6 +55,9 @@
 
 constexpr std::chrono::duration VSYNC_PERIOD(16ms);
 
+constexpr int HDCP_V1 = 2;
+constexpr int HDCP_V2 = 3;
+
 } // namespace
 
 class EventThreadTest : public testing::Test, public IEventThreadCallback {
@@ -82,6 +85,7 @@
     bool throttleVsync(TimePoint, uid_t) override;
     Period getVsyncPeriod(uid_t) override;
     void resync() override;
+    void onExpectedPresentTimePosted(TimePoint) override;
 
     void setupEventThread();
     sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
@@ -104,6 +108,7 @@
                                                       int32_t expectedConfigId,
                                                       nsecs_t expectedVsyncPeriod);
     void expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t);
+    void expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime);
     void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                             std::vector<FrameRateOverride>);
 
@@ -112,14 +117,16 @@
         mThread->onVsync(expectedPresentationTime, timestamp, deadlineTimestamp);
     }
 
+    static constexpr scheduler::ScheduleResult kScheduleResult{TimePoint::fromNs(0),
+                                                               TimePoint::fromNs(0)};
     AsyncCallRecorderWithCannedReturn<
             scheduler::ScheduleResult (*)(scheduler::VSyncDispatch::CallbackToken,
                                           scheduler::VSyncDispatch::ScheduleTiming)>
-            mVSyncCallbackScheduleRecorder{0};
+            mVSyncCallbackScheduleRecorder{kScheduleResult};
     AsyncCallRecorderWithCannedReturn<
             scheduler::ScheduleResult (*)(scheduler::VSyncDispatch::CallbackToken,
                                           scheduler::VSyncDispatch::ScheduleTiming)>
-            mVSyncCallbackUpdateRecorder{0};
+            mVSyncCallbackUpdateRecorder{kScheduleResult};
     AsyncCallRecorderWithCannedReturn<
             scheduler::VSyncDispatch::CallbackToken (*)(scheduler::VSyncDispatch::Callback,
                                                         std::string)>
@@ -128,6 +135,7 @@
             mVSyncCallbackUnregisterRecorder;
     AsyncCallRecorder<void (*)()> mResyncCallRecorder;
     AsyncCallRecorder<void (*)(nsecs_t, uid_t)> mThrottleVsyncCallRecorder;
+    AsyncCallRecorder<void (*)(nsecs_t)> mOnExpectedPresentTimePostedRecorder;
     ConnectionEventRecorder mConnectionEventCallRecorder{0};
     ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
 
@@ -187,6 +195,10 @@
     mResyncCallRecorder.recordCall();
 }
 
+void EventThreadTest::onExpectedPresentTimePosted(TimePoint expectedPresentTime) {
+    mOnExpectedPresentTimePostedRecorder.recordCall(expectedPresentTime.ns());
+}
+
 void EventThreadTest::setupEventThread() {
     mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
     mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
@@ -241,6 +253,12 @@
     EXPECT_EQ(uid, std::get<1>(args.value()));
 }
 
+void EventThreadTest::expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime) {
+    auto args = mOnExpectedPresentTimePostedRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    EXPECT_EQ(expectedPresentTime, std::get<0>(args.value()));
+}
+
 void EventThreadTest::expectVsyncEventReceivedByConnection(
         const char* name, ConnectionEventRecorder& connectionEventRecorder,
         nsecs_t expectedTimestamp, unsigned expectedCount) {
@@ -407,6 +425,7 @@
     onVSyncEvent(123, 456, 789);
     expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
+    expectOnExpectedPresentTimePosted(456);
 
     // EventThread is requesting one more callback due to VsyncRequest::SingleSuppressCallback
     expectVSyncCallbackScheduleReceived(true);
@@ -471,10 +490,10 @@
 
     mock::VSyncTracker& mockTracker =
             *static_cast<mock::VSyncTracker*>(&mVsyncSchedule->getTracker());
-    EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_, _))
             .WillOnce(Return(preferredExpectedPresentationTime));
 
-    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
+    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection, now);
 
     // Check EventThread immediately requested a resync.
     EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
@@ -559,16 +578,19 @@
     onVSyncEvent(123, 456, 789);
     expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
+    expectOnExpectedPresentTimePosted(456);
 
     // A second event should go to the same places.
     onVSyncEvent(456, 123, 0);
     expectThrottleVsyncReceived(123, mConnectionUid);
     expectVsyncEventReceivedByConnection(456, 2u);
+    expectOnExpectedPresentTimePosted(123);
 
     // A third event should go to the same places.
     onVSyncEvent(789, 777, 111);
     expectThrottleVsyncReceived(777, mConnectionUid);
     expectVsyncEventReceivedByConnection(789, 3u);
+    expectOnExpectedPresentTimePosted(777);
 }
 
 TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
@@ -833,6 +855,19 @@
     expectVSyncCallbackScheduleReceived(true);
 }
 
+TEST_F(EventThreadTest, postHcpLevelsChanged) {
+    setupEventThread();
+
+    mThread->onHdcpLevelsChanged(EXTERNAL_DISPLAY_ID, HDCP_V1, HDCP_V2);
+    auto args = mConnectionEventCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    const auto& event = std::get<0>(args.value());
+    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE, event.header.type);
+    EXPECT_EQ(EXTERNAL_DISPLAY_ID, event.header.displayId);
+    EXPECT_EQ(HDCP_V1, event.hdcpLevelsChange.connectedLevel);
+    EXPECT_EQ(HDCP_V2, event.hdcpLevelsChange.maxLevel);
+}
+
 } // namespace
 } // namespace android
 
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index 0c820fb..51b5f40 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -28,6 +28,7 @@
 
 namespace android {
 
+using namespace com::android::graphics::surfaceflinger;
 using testing::Return;
 
 class TestableFlagManager : public FlagManager {
@@ -84,28 +85,28 @@
     EXPECT_EQ(false, mFlagManager.test_flag());
 }
 
-TEST_F(FlagManagerTest, creashesIfQueriedBeforeBoot) {
+TEST_F(FlagManagerTest, crashesIfQueriedBeforeBoot) {
     mFlagManager.markBootIncomplete();
-    EXPECT_DEATH(FlagManager::getInstance().late_boot_misc2(), "");
+    EXPECT_DEATH(FlagManager::getInstance()
+        .refresh_rate_overlay_on_external_display(), "");
 }
 
 TEST_F(FlagManagerTest, returnsOverrideTrue) {
     mFlagManager.markBootCompleted();
 
-    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, false);
+    SET_FLAG_FOR_TEST(flags::refresh_rate_overlay_on_external_display, false);
 
     // This is stored in a static variable, so this test depends on the fact
     // that this flag has not been read in this process.
     EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(true));
-    EXPECT_TRUE(mFlagManager.late_boot_misc2());
+    EXPECT_TRUE(mFlagManager.refresh_rate_overlay_on_external_display());
 
     // Further calls will not result in further calls to getBoolProperty.
-    EXPECT_TRUE(mFlagManager.late_boot_misc2());
+    EXPECT_TRUE(mFlagManager.refresh_rate_overlay_on_external_display());
 }
 
 TEST_F(FlagManagerTest, returnsOverrideReadonly) {
-    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace,
-                      false);
+    SET_FLAG_FOR_TEST(flags::add_sf_skipped_frames_to_trace, false);
 
     // This is stored in a static variable, so this test depends on the fact
     // that this flag has not been read in this process.
@@ -113,12 +114,13 @@
     EXPECT_TRUE(mFlagManager.add_sf_skipped_frames_to_trace());
 }
 
-TEST_F(FlagManagerTest, returnsOverrideFalse) {
+// disabling this test since we need to use a unique flag for this test,
+// but we only one server flag currently. Re-enable once we have a new flag
+// and change this test to use a unique flag.
+TEST_F(FlagManagerTest, DISABLED_returnsOverrideFalse) {
     mFlagManager.markBootCompleted();
 
-    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
-                              refresh_rate_overlay_on_external_display,
-                      true);
+    SET_FLAG_FOR_TEST(flags::refresh_rate_overlay_on_external_display, true);
 
     // This is stored in a static variable, so this test depends on the fact
     // that this flag has not been read in this process.
@@ -129,7 +131,7 @@
 TEST_F(FlagManagerTest, ignoresOverrideInUnitTestMode) {
     mFlagManager.setUnitTestMode();
 
-    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::multithreaded_present, true);
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
 
     // If this has not been called in this process, it will be called.
     // Regardless, the result is ignored.
@@ -144,13 +146,13 @@
     EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
 
     {
-        SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, true);
-        EXPECT_EQ(true, mFlagManager.late_boot_misc2());
+        SET_FLAG_FOR_TEST(flags::refresh_rate_overlay_on_external_display, true);
+        EXPECT_EQ(true, mFlagManager.refresh_rate_overlay_on_external_display());
     }
 
     {
-        SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, false);
-        EXPECT_EQ(false, mFlagManager.late_boot_misc2());
+        SET_FLAG_FOR_TEST(flags::refresh_rate_overlay_on_external_display, false);
+        EXPECT_EQ(false, mFlagManager.refresh_rate_overlay_on_external_display());
     }
 }
 
@@ -160,28 +162,14 @@
     EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
 
     {
-        SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::misc1, true);
+        SET_FLAG_FOR_TEST(flags::misc1, true);
         EXPECT_EQ(true, mFlagManager.misc1());
     }
 
     {
-        SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::misc1, false);
+        SET_FLAG_FOR_TEST(flags::misc1, false);
         EXPECT_EQ(false, mFlagManager.misc1());
     }
 }
 
-TEST_F(FlagManagerTest, dontSkipOnEarlyIsNotCached) {
-    EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
-
-    const auto initialValue = com::android::graphics::surfaceflinger::flags::dont_skip_on_early();
-
-    com::android::graphics::surfaceflinger::flags::dont_skip_on_early(true);
-    EXPECT_EQ(true, mFlagManager.dont_skip_on_early());
-
-    com::android::graphics::surfaceflinger::flags::dont_skip_on_early(false);
-    EXPECT_EQ(false, mFlagManager.dont_skip_on_early());
-
-    com::android::graphics::surfaceflinger::flags::dont_skip_on_early(initialValue);
-}
-
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
index f695b09..9e8e306 100644
--- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
@@ -24,7 +24,11 @@
 #include <gtest/gtest.h>
 #include <gui/LayerMetadata.h>
 
+#include "Client.h" // temporarily needed for LayerCreationArgs
 #include "FpsReporter.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
 #include "Layer.h"
 #include "TestableSurfaceFlinger.h"
 #include "fake/FakeClock.h"
@@ -76,7 +80,15 @@
 
     sp<Layer> createBufferStateLayer(LayerMetadata metadata);
 
-    TestableSurfaceFlinger mFlinger;
+    LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+                                 LayerMetadata metadata);
+
+    void createRootLayer(uint32_t id, LayerMetadata metadata);
+
+    void createLayer(uint32_t id, uint32_t parentId, LayerMetadata metadata);
+
+    frontend::LayerLifecycleManager mLifecycleManager;
+
     mock::FrameTimeline mFrameTimeline =
             mock::FrameTimeline(std::make_shared<impl::TimeStats>(), 0);
 
@@ -89,8 +101,8 @@
 
     sp<TestableFpsListener> mFpsListener;
     fake::FakeClock* mClock = new fake::FakeClock();
-    sp<FpsReporter> mFpsReporter = sp<FpsReporter>::make(mFrameTimeline, *(mFlinger.flinger()),
-                                                         std::unique_ptr<Clock>(mClock));
+    sp<FpsReporter> mFpsReporter =
+            sp<FpsReporter>::make(mFrameTimeline, std::unique_ptr<Clock>(mClock));
 };
 
 FpsReporterTest::FpsReporterTest() {
@@ -98,9 +110,6 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    mFlinger.setupMockScheduler();
-    mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
-
     mFpsListener = sp<TestableFpsListener>::make();
 }
 
@@ -110,76 +119,94 @@
     ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
 }
 
-sp<Layer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) {
+LayerCreationArgs FpsReporterTest::createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+                                              LayerMetadata metadata) {
     sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata);
-    return sp<Layer>::make(args);
+    LayerCreationArgs args(std::make_optional(id));
+    args.name = "testlayer";
+    args.addToRoot = canBeRoot;
+    args.flags = LAYER_FLAGS;
+    args.metadata = metadata;
+    args.parentId = parentId;
+    return args;
+}
+
+void FpsReporterTest::createRootLayer(uint32_t id, LayerMetadata metadata = LayerMetadata()) {
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> layers;
+    layers.emplace_back(std::make_unique<frontend::RequestedLayerState>(
+            createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID,
+                       /*metadata=*/metadata)));
+    mLifecycleManager.addLayers(std::move(layers));
+}
+
+void FpsReporterTest::createLayer(uint32_t id, uint32_t parentId,
+                                  LayerMetadata metadata = LayerMetadata()) {
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> layers;
+    layers.emplace_back(std::make_unique<frontend::RequestedLayerState>(
+            createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+                       /*mirror=*/metadata)));
+    mLifecycleManager.addLayers(std::move(layers));
 }
 
 namespace {
 
 TEST_F(FpsReporterTest, callsListeners) {
-    mParent = createBufferStateLayer();
     constexpr int32_t kTaskId = 12;
     LayerMetadata targetMetadata;
     targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId);
-    mTarget = createBufferStateLayer(targetMetadata);
-    mChild = createBufferStateLayer();
-    mGrandChild = createBufferStateLayer();
-    mUnrelated = createBufferStateLayer();
-    mParent->addChild(mTarget);
-    mTarget->addChild(mChild);
-    mChild->addChild(mGrandChild);
-    mParent->commitChildList();
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mParent);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mChild);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mGrandChild);
+
+    createRootLayer(1, targetMetadata);
+    createLayer(11, 1);
+    createLayer(111, 11);
+
+    frontend::LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     float expectedFps = 44.0;
 
-    EXPECT_CALL(mFrameTimeline,
-                computeFps(UnorderedElementsAre(mTarget->getSequence(), mChild->getSequence(),
-                                                mGrandChild->getSequence())))
+    EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(1, 11, 111)))
             .WillOnce(Return(expectedFps));
 
     mFpsReporter->addListener(mFpsListener, kTaskId);
     mClock->advanceTime(600ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(expectedFps, mFpsListener->lastReportedFps);
     mFpsReporter->removeListener(mFpsListener);
     Mock::VerifyAndClearExpectations(&mFrameTimeline);
 
     EXPECT_CALL(mFrameTimeline, computeFps(_)).Times(0);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
 }
 
 TEST_F(FpsReporterTest, rateLimits) {
     const constexpr int32_t kTaskId = 12;
     LayerMetadata targetMetadata;
     targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId);
-    mTarget = createBufferStateLayer(targetMetadata);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget);
+    createRootLayer(1);
+    createLayer(11, 1, targetMetadata);
+
+    frontend::LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     float firstFps = 44.0;
     float secondFps = 53.0;
 
-    EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(mTarget->getSequence())))
+    EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(11)))
             .WillOnce(Return(firstFps))
             .WillOnce(Return(secondFps));
 
     mFpsReporter->addListener(mFpsListener, kTaskId);
     mClock->advanceTime(600ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
     mClock->advanceTime(200ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
     mClock->advanceTime(200ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
     mClock->advanceTime(200ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(secondFps, mFpsListener->lastReportedFps);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index ddc3967..dac9265 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -341,6 +341,57 @@
     EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt);
 }
 
+TEST_F(FrameTimelineTest, displayFrameSkippedComposition) {
+    // Layer specific increment
+    EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(1);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30});
+    FrameTimelineInfo ftInfo;
+    ftInfo.vsyncId = surfaceFrameToken1;
+    ftInfo.inputEventId = sInputEventId;
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdTwo,
+                                                       sLayerNameTwo, sLayerNameTwo,
+                                                       /*isBuffer*/ true, sGameMode);
+
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    mFrameTimeline->onCommitNotComposited();
+
+    EXPECT_EQ(surfaceFrame1->getActuals().presentTime, 30);
+    ASSERT_NE(surfaceFrame1->getJankType(), std::nullopt);
+    EXPECT_EQ(*surfaceFrame1->getJankType(), JankType::None);
+    ASSERT_NE(surfaceFrame1->getJankSeverityType(), std::nullopt);
+    EXPECT_EQ(*surfaceFrame1->getJankSeverityType(), JankSeverityType::None);
+
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    mFrameTimeline->setSfPresent(26, presentFence1);
+
+    auto displayFrame = getDisplayFrame(0);
+    auto& presentedSurfaceFrame2 = getSurfaceFrame(0, 0);
+    presentFence1->signalForTest(42);
+
+    // Fences haven't been flushed yet, so it should be 0
+    EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+    EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 0);
+
+    addEmptyDisplayFrame();
+
+    // Fences have flushed, so the present timestamps should be updated
+    EXPECT_EQ(displayFrame->getActuals().presentTime, 42);
+    EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 42);
+    EXPECT_NE(surfaceFrame2->getJankType(), std::nullopt);
+    EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt);
+}
+
 TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) {
     // Insert kMaxDisplayFrames' count of DisplayFrames to fill the deque
     int frameTimeFactor = 0;
@@ -1081,6 +1132,72 @@
     EXPECT_EQ(received.cookie(), source.cookie());
 }
 
+TEST_F(FrameTimelineTest, traceDisplayFrameNoSkipped) {
+    // setup 2 display frames
+    // DF 1: [22, 30] -> [0, 11]
+    // DF 2: [82, 90] -> SF [5, 16]
+    auto tracingSession = getTracingSessionForTest();
+    tracingSession->StartBlocking();
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 100});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({0, 11, 25});
+    int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({5, 16, 30});
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    int64_t traceCookie = snoopCurrentTraceCookie();
+
+    // set up 1st display frame
+    FrameTimelineInfo ftInfo1;
+    ftInfo1.vsyncId = surfaceFrameToken1;
+    ftInfo1.inputEventId = sInputEventId;
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo1, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+    surfaceFrame1->setAcquireFenceTime(11);
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_30);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    mFrameTimeline->setSfPresent(30, presentFence1);
+    presentFence1->signalForTest(40);
+
+    // Trigger a flush by finalizing the next DisplayFrame
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    FrameTimelineInfo ftInfo2;
+    ftInfo2.vsyncId = surfaceFrameToken2;
+    ftInfo2.inputEventId = sInputEventId;
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+
+    // set up 2nd display frame
+    surfaceFrame2->setAcquireFenceTime(16);
+    mFrameTimeline->setSfWakeUp(sfToken2, 82, RR_11, RR_30);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    mFrameTimeline->setSfPresent(90, presentFence2);
+    presentFence2->signalForTest(100);
+
+    // the token of skipped Display Frame
+    auto protoSkippedActualDisplayFrameStart =
+            createProtoActualDisplayFrameStart(traceCookie + 9, 0, kSurfaceFlingerPid,
+                                               FrameTimelineEvent::PRESENT_DROPPED, true, false,
+                                               FrameTimelineEvent::JANK_DROPPED,
+                                               FrameTimelineEvent::SEVERITY_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
+    auto protoSkippedActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 9);
+
+    // Trigger a flush by finalizing the next DisplayFrame
+    addEmptyDisplayFrame();
+    flushTrace();
+    tracingSession->StopBlocking();
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    // 8 Valid Display Frames + 8 Valid Surface Frames + no Skipped Display Frames
+    EXPECT_EQ(packets.size(), 16u);
+}
+
 TEST_F(FrameTimelineTest, traceDisplayFrameSkipped) {
     SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace,
                       true);
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 6edecff..2cff2f2 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -21,6 +21,7 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <optional>
 #include <vector>
 
 // StrictMock<T> derives from T and is not marked final, so the destructor of T is expected to be
@@ -82,6 +83,8 @@
         EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE));
         EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId));
     }
+
+    void setVrrTimeoutHint(bool status) { mHwc.mEnableVrrTimeout = status; }
 };
 
 TEST_F(HWComposerTest, isHeadless) {
@@ -99,9 +102,32 @@
     ASSERT_TRUE(mHwc.isHeadless());
 }
 
+TEST_F(HWComposerTest, getDisplayConnectionType) {
+    // Unknown display.
+    EXPECT_EQ(mHwc.getDisplayConnectionType(PhysicalDisplayId::fromPort(0)),
+              ui::DisplayConnectionType::Internal);
+
+    constexpr hal::HWDisplayId kHwcDisplayId = 1;
+    expectHotplugConnect(kHwcDisplayId);
+
+    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    ASSERT_TRUE(info);
+
+    EXPECT_CALL(*mHal, getDisplayConnectionType(kHwcDisplayId, _))
+            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::EXTERNAL),
+                            Return(V2_4::Error::NONE)));
+
+    // The first call caches the connection type.
+    EXPECT_EQ(mHwc.getDisplayConnectionType(info->id), ui::DisplayConnectionType::External);
+
+    // Subsequent calls return the cached connection type.
+    EXPECT_EQ(mHwc.getDisplayConnectionType(info->id), ui::DisplayConnectionType::External);
+    EXPECT_EQ(mHwc.getDisplayConnectionType(info->id), ui::DisplayConnectionType::External);
+}
+
 TEST_F(HWComposerTest, getActiveMode) {
     // Unknown display.
-    EXPECT_EQ(mHwc.getActiveMode(PhysicalDisplayId::fromPort(0)), std::nullopt);
+    EXPECT_EQ(mHwc.getActiveMode(PhysicalDisplayId::fromPort(0)), ftl::Unexpected(BAD_INDEX));
 
     constexpr hal::HWDisplayId kHwcDisplayId = 2;
     expectHotplugConnect(kHwcDisplayId);
@@ -114,14 +140,20 @@
         EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _))
                 .WillOnce(Return(HalError::BAD_DISPLAY));
 
-        EXPECT_EQ(mHwc.getActiveMode(info->id), std::nullopt);
+        EXPECT_EQ(mHwc.getActiveMode(info->id), ftl::Unexpected(UNKNOWN_ERROR));
+    }
+    {
+        EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _))
+                .WillOnce(Return(HalError::BAD_CONFIG));
+
+        EXPECT_EQ(mHwc.getActiveMode(info->id), ftl::Unexpected(NO_INIT));
     }
     {
         constexpr hal::HWConfigId kConfigId = 42;
         EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _))
                 .WillOnce(DoAll(SetArgPointee<1>(kConfigId), Return(HalError::NONE)));
 
-        EXPECT_EQ(mHwc.getActiveMode(info->id), kConfigId);
+        EXPECT_EQ(mHwc.getActiveMode(info->id).value_opt(), kConfigId);
     }
 }
 
@@ -323,6 +355,7 @@
         EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
     }
     {
+        setVrrTimeoutHint(true);
         constexpr int32_t kWidth = 480;
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
@@ -330,10 +363,8 @@
         const hal::VrrConfig vrrConfig =
                 hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
                                .notifyExpectedPresentConfig = hal::VrrConfig::
-                                       NotifyExpectedPresentConfig{.notifyExpectedPresentHeadsUpNs =
-                                                                           ms2ns(30),
-                                                                   .notifyExpectedPresentTimeoutNs =
-                                                                           ms2ns(30)}};
+                                       NotifyExpectedPresentConfig{.headsUpNs = ms2ns(30),
+                                                                   .timeoutNs = ms2ns(30)}};
         hal::DisplayConfiguration displayConfiguration{.configId = kConfigId,
                                                        .width = kWidth,
                                                        .height = kHeight,
@@ -363,9 +394,9 @@
         displayConfiguration.dpi = {kDpi, kDpi};
 
         EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
-                .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
-                                        displayConfiguration}),
-                                Return(HalError::NONE)));
+                .WillRepeatedly(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
+                                              displayConfiguration}),
+                                      Return(HalError::NONE)));
 
         modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
         EXPECT_EQ(modes.size(), size_t{1});
@@ -377,6 +408,10 @@
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
         EXPECT_EQ(modes.front().dpiX, kDpi);
         EXPECT_EQ(modes.front().dpiY, kDpi);
+
+        setVrrTimeoutHint(false);
+        modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.front().vrrConfig->notifyExpectedPresentConfig, std::nullopt);
     }
 }
 
@@ -437,7 +472,7 @@
                                     {kMetadata1Name, kMetadata1Mandatory},
                                     {kMetadata2Name, kMetadata2Mandatory},
                             }),
-                            Return(hardware::graphics::composer::V2_4::Error::NONE)));
+                            Return(V2_4::Error::NONE)));
     EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::NONE));
     EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::NONE));
 
@@ -455,8 +490,7 @@
 
 TEST_F(HWComposerSetCallbackTest, handlesUnsupportedCallToGetLayerGenericMetadataKeys) {
     EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<aidl::Capability>{}));
-    EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
-            .WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
+    EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_)).WillOnce(Return(V2_4::Error::UNSUPPORTED));
     EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::UNSUPPORTED));
     EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::UNSUPPORTED));
     EXPECT_CALL(*mHal, registerCallback(_));
@@ -516,7 +550,7 @@
                 setLayerGenericMetadata(kDisplayId, kLayerId, kLayerGenericMetadata1Name,
                                         kLayerGenericMetadata1Mandatory,
                                         kLayerGenericMetadata1Value))
-            .WillOnce(Return(hardware::graphics::composer::V2_4::Error::NONE));
+            .WillOnce(Return(V2_4::Error::NONE));
     auto result = mLayer.setLayerGenericMetadata(kLayerGenericMetadata1Name,
                                                  kLayerGenericMetadata1Mandatory,
                                                  kLayerGenericMetadata1Value);
@@ -526,7 +560,7 @@
                 setLayerGenericMetadata(kDisplayId, kLayerId, kLayerGenericMetadata2Name,
                                         kLayerGenericMetadata2Mandatory,
                                         kLayerGenericMetadata2Value))
-            .WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
+            .WillOnce(Return(V2_4::Error::UNSUPPORTED));
     result = mLayer.setLayerGenericMetadata(kLayerGenericMetadata2Name,
                                             kLayerGenericMetadata2Mandatory,
                                             kLayerGenericMetadata2Value);
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 95f1940..2b333f4 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -45,7 +45,8 @@
 
 // reparenting tests
 TEST_F(LayerHierarchyTest, addLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -64,7 +65,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(2, 11);
     reparentLayer(111, 12);
     reparentLayer(1221, 1);
@@ -79,7 +81,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayerToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(2, UNASSIGNED_LAYER_ID);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
@@ -96,7 +99,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayerToNullAndDestroyHandles) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(2, UNASSIGNED_LAYER_ID);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     reparentLayer(1221, UNASSIGNED_LAYER_ID);
@@ -115,7 +119,8 @@
 }
 
 TEST_F(LayerHierarchyTest, destroyHandleThenDestroyParentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     destroyLayerHandle(111);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -139,7 +144,8 @@
 }
 
 TEST_F(LayerHierarchyTest, layerSurvivesTemporaryReparentToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     reparentLayer(11, 1);
 
@@ -154,7 +160,8 @@
 
 // offscreen tests
 TEST_F(LayerHierarchyTest, layerMovesOnscreen) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -170,7 +177,8 @@
 }
 
 TEST_F(LayerHierarchyTest, addLayerToOffscreenParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -187,7 +195,8 @@
 
 // rel-z tests
 TEST_F(LayerHierarchyTest, setRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -200,7 +209,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentFromRelativeParentWithSetLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -216,7 +226,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentToRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -231,7 +242,8 @@
 }
 
 TEST_F(LayerHierarchyTest, setParentAsRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -246,7 +258,8 @@
 }
 
 TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -262,7 +275,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentRelativeLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -294,7 +308,8 @@
 
 // mirror tests
 TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -308,7 +323,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorOffscreenLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
@@ -324,7 +340,8 @@
 TEST_F(LayerHierarchyTest, newChildLayerIsUpdatedInMirrorHierarchy) {
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     createLayer(1111, 111);
     createLayer(112, 11);
@@ -340,7 +357,8 @@
 
 // mirror & relatives tests
 TEST_F(LayerHierarchyTest, mirrorWithRelativeOutsideMirrorHierarchy) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(111, 12);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
 
@@ -371,7 +389,8 @@
 }
 
 TEST_F(LayerHierarchyTest, mirrorWithRelativeInsideMirrorHierarchy) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(1221, 12);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 12);
 
@@ -401,7 +420,8 @@
 }
 
 TEST_F(LayerHierarchyTest, childMovesOffscreenWhenRelativeParentDies) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentRelativeLayer(11, 2);
     reparentLayer(2, UNASSIGNED_LAYER_ID);
@@ -427,7 +447,8 @@
 }
 
 TEST_F(LayerHierarchyTest, offscreenLayerCannotBeRelativeToOnscreenLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(1221, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -462,7 +483,8 @@
 }
 
 TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     updateBackgroundColor(1, 0.5);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -485,7 +507,8 @@
     createLayer(11, 1);
     reparentLayer(1, 11);
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     std::vector<uint32_t> expectedTraversalPath = {};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -502,17 +525,11 @@
     createLayer(11, 1);
     reparentRelativeLayer(11, 2);
     reparentRelativeLayer(2, 11);
-    mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
-
-    // fix loop
-    uint32_t invalidRelativeRoot;
-    bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
-    EXPECT_TRUE(hasRelZLoop);
-    mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
-    hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
-    EXPECT_EQ(invalidRelativeRoot, 11u);
-    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+    LayerHierarchyBuilder hierarchyBuilder;
+    // this call is expected to fix the loop!
+    hierarchyBuilder.update(mLifecycleManager);
+    uint32_t unused;
+    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(unused));
 
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -534,16 +551,11 @@
     createLayer(221, 22);
     reparentRelativeLayer(22, 111);
     reparentRelativeLayer(11, 221);
-    mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
-
-    // fix loop
-    uint32_t invalidRelativeRoot;
-    bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
-    EXPECT_TRUE(hasRelZLoop);
-    mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
-    hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
-    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+    LayerHierarchyBuilder hierarchyBuilder;
+    // this call is expected to fix the loop!
+    hierarchyBuilder.update(mLifecycleManager);
+    uint32_t unused;
+    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(unused));
 
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 22, 221, 2, 21, 22, 221};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -554,7 +566,8 @@
 }
 
 TEST_F(LayerHierarchyTest, ReparentRootLayerToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(1, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -568,7 +581,8 @@
 TEST_F(LayerHierarchyTest, AddRemoveLayerInSameTransaction) {
     // remove default hierarchy
     mLifecycleManager = LayerLifecycleManager();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     createRootLayer(1);
     destroyLayerHandle(1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -582,7 +596,8 @@
 // traversal path test
 TEST_F(LayerHierarchyTest, traversalPathId) {
     setZ(122, -1);
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     auto checkTraversalPathIdVisitor =
             [](const LayerHierarchy& hierarchy,
                const LayerHierarchy::TraversalPath& traversalPath) -> bool {
@@ -605,7 +620,8 @@
     createLayer(53, 5);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 4, 5, 51, 53};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -639,7 +655,8 @@
     setZ(13, 1);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 13, 12};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -661,7 +678,8 @@
     setLayerStack(2, 10);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {2, 21, 1, 11};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -672,7 +690,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorDisplay) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -687,7 +706,8 @@
 }
 
 TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(5));
     setLayerStack(3, 1);
@@ -701,7 +721,8 @@
 }
 
 TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -719,7 +740,8 @@
 }
 
 TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -737,7 +759,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorDisplayWithMirrors) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(12, UNASSIGNED_LAYER_ID);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     UPDATE_AND_VERIFY(hierarchyBuilder);
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 7e9abce..8b3303c 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -155,6 +155,17 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setPosition(uint32_t id, float x, float y) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().state.what = layer_state_t::ePositionChanged;
+        transactions.back().states.front().state.x = x;
+        transactions.back().states.front().state.y = y;
+        transactions.back().states.front().layerId = id;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     virtual void mirrorLayer(uint32_t id, uint32_t parentId, uint32_t layerIdToMirror) {
         std::vector<std::unique_ptr<RequestedLayerState>> layers;
         layers.emplace_back(std::make_unique<RequestedLayerState>(
@@ -176,14 +187,12 @@
     void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); }
 
     void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
-        if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            hierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                    mLifecycleManager.getDestroyedLayers());
-        }
+        hierarchyBuilder.update(mLifecycleManager);
         mLifecycleManager.commitChanges();
 
         // rebuild layer hierarchy from scratch and verify that it matches the updated state.
-        LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
+        LayerHierarchyBuilder newBuilder;
+        newBuilder.update(mLifecycleManager);
         EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
                   getTraversalPath(newBuilder.getHierarchy()));
         EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
@@ -283,6 +292,24 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setInputInfo(uint32_t id, std::function<void(gui::WindowInfo&)> configureInput) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.windowInfoHandle =
+                sp<gui::WindowInfoHandle>::make();
+        auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo();
+        if (!inputInfo->token) {
+            inputInfo->token = sp<BBinder>::make();
+        }
+        configureInput(*inputInfo);
+
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setTouchableRegionCrop(uint32_t id, Region region, uint32_t touchCropId,
                                 bool replaceTouchableRegionWithCrop) {
         std::vector<TransactionState> transactions;
@@ -467,14 +494,14 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
-    void setTrustedOverlay(uint32_t id, bool isTrustedOverlay) {
+    void setTrustedOverlay(uint32_t id, gui::TrustedOverlay trustedOverlay) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
         transactions.back().states.front().state.what = layer_state_t::eTrustedOverlayChanged;
         transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.isTrustedOverlay = isTrustedOverlay;
+        transactions.back().states.front().state.trustedOverlay = trustedOverlay;
         mLifecycleManager.applyTransactions(transactions);
     }
 
@@ -512,10 +539,7 @@
     }
 
     void update(LayerSnapshotBuilder& snapshotBuilder) {
-        if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            mHierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                     mLifecycleManager.getDestroyedLayers());
-        }
+        mHierarchyBuilder.update(mLifecycleManager);
         LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
                                         .layerLifecycleManager = mLifecycleManager,
                                         .includeMetadata = false,
@@ -530,7 +554,7 @@
         mLifecycleManager.commitChanges();
     }
 
-    LayerHierarchyBuilder mHierarchyBuilder{{}};
+    LayerHierarchyBuilder mHierarchyBuilder;
 
     DisplayInfos mFrontEndDisplayInfos;
     bool mHasDisplayChanges = false;
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 8a412f8..a61fa1e 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -56,7 +56,7 @@
     LayerHistoryIntegrationTest() : LayerSnapshotTestBase() {
         mFlinger.resetScheduler(mScheduler);
         mLifecycleManager = {};
-        mHierarchyBuilder = {{}};
+        mHierarchyBuilder = {};
     }
 
     void updateLayerSnapshotsAndLayerHistory(nsecs_t now) {
@@ -165,12 +165,8 @@
                                                   DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
-
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
-
     TestableSurfaceFlinger mFlinger;
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
@@ -278,6 +274,8 @@
 }
 
 TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     createLegacyAndFrontedEndLayer(1);
     setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
 
@@ -1005,8 +1003,6 @@
         mappings.push_back(std::make_pair(kAppId1, kThreshold1));
         mappings.push_back(std::make_pair(kAppId2, kThreshold2));
 
-        mFlinger.enableNewFrontEnd();
-
         mScheduler->onActiveDisplayAreaChanged(DISPLAY_WIDTH * DISPLAY_HEIGHT);
         mScheduler->updateSmallAreaDetection(mappings);
     }
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 0ae3ca3..088d0d2 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -46,6 +46,7 @@
 using MockLayer = android::mock::MockLayer;
 
 using android::mock::createDisplayMode;
+using android::mock::createVrrDisplayMode;
 
 // WARNING: LEGACY TESTS FOR LEGACY FRONT END
 // Update LayerHistoryIntegrationTest instead
@@ -138,20 +139,18 @@
         ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
     }
 
-    std::shared_ptr<RefreshRateSelector> mSelector =
-            std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
-                                                                              LO_FPS),
-                                                            createDisplayMode(DisplayModeId(1),
-                                                                              HI_FPS)),
-                                                  DisplayModeId(0));
+    static constexpr auto kVrrModeId = DisplayModeId(2);
+    std::shared_ptr<RefreshRateSelector> mSelector = std::make_shared<RefreshRateSelector>(
+            makeModes(createDisplayMode(DisplayModeId(0), LO_FPS),
+                      createDisplayMode(DisplayModeId(1), HI_FPS),
+                      createVrrDisplayMode(kVrrModeId, HI_FPS,
+                                           hal::VrrConfig{.minFrameIntervalNs =
+                                                                  HI_FPS.getPeriodNsecs()})),
+            DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
-
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
-
     TestableSurfaceFlinger mFlinger;
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
@@ -507,7 +506,7 @@
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
-    // layer became inactive, but the vote stays
+    // layer became infrequent, but the vote stays
     setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -541,7 +540,7 @@
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
-    // layer became inactive, but the vote stays
+    // layer became infrequent, but the vote stays
     setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -552,7 +551,122 @@
     EXPECT_EQ(0, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, oneLayerExplicitGte_vrr) {
+    // Set the test to be on a vrr mode.
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    mSelector->setActiveMode(kVrrModeId, HI_FPS);
+
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(Layer::FrameRate(33_Hz, Layer::FrameRateCompatibility::Gte,
+                                                    Seamlessness::OnlySeamless,
+                                                    FrameRateCategory::Default)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitGte, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(33_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // layer became inactive, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitGte, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(33_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+// Test for MRR device with VRR features enabled.
+TEST_F(LayerHistoryTest, oneLayerExplicitGte_nonVrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+    // The vrr_config flag is explicitly not set false because this test for an MRR device
+    // should still work in a VRR-capable world.
+
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(Layer::FrameRate(33_Hz, Layer::FrameRateCompatibility::Gte,
+                                                    Seamlessness::OnlySeamless,
+                                                    FrameRateCategory::Default)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // layer became infrequent, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory_vrrFeatureOff) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(
+                    Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default,
+                                            Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+    // Set default to Min so it is obvious that the vote reset triggered.
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    // There is only 1 LayerRequirement due to the disabled flag frame_rate_category_mrr.
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
 TEST_F(LayerHistoryTest, oneLayerExplicitCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
@@ -578,7 +692,7 @@
     EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
 
-    // layer became inactive, but the vote stays
+    // layer became infrequent, but the vote stays
     setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -592,6 +706,8 @@
 // This test case should be the same as oneLayerNoVote except instead of layer vote is NoVote,
 // the category is NoPreference.
 TEST_F(LayerHistoryTest, oneLayerCategoryNoPreference) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
@@ -613,7 +729,7 @@
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
-    // layer became inactive
+    // layer became infrequent
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     EXPECT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(1, activeLayerCount());
@@ -621,6 +737,8 @@
 }
 
 TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
@@ -650,9 +768,9 @@
     // Second LayerRequirement is the frame rate specification
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[1].vote);
     EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate);
-    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[1].frameRateCategory);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[1].frameRateCategory);
 
-    // layer became inactive, but the vote stays
+    // layer became infrequent, but the vote stays
     setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(2, summarizeLayerHistory(time).size());
@@ -1057,7 +1175,7 @@
     EXPECT_EQ(0, frequentLayerCount(time));
     EXPECT_EQ(0, animatingLayerCount(time));
 
-    // layer became inactive
+    // Layer still active due to front buffering, but it's infrequent.
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 07a522a..c1fa6ac 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -64,10 +64,8 @@
                                                                               HI_FPS)),
                                                   DisplayModeId(0));
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
     TestableSurfaceFlinger mFlinger;
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
@@ -215,7 +213,8 @@
 TEST_F(LayerInfoTest, getRefreshRateVote_explicitVoteWithCategory) {
     LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
                                  .fps = 20_Hz,
-                                 .category = FrameRateCategory::High};
+                                 .category = FrameRateCategory::High,
+                                 .categorySmoothSwitchOnly = true};
     layerInfo.setLayerVote(vote);
 
     auto actualVotes =
@@ -223,10 +222,12 @@
     ASSERT_EQ(actualVotes.size(), 2u);
     ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
     ASSERT_EQ(actualVotes[0].category, vote.category);
+    ASSERT_TRUE(actualVotes[0].categorySmoothSwitchOnly);
     ASSERT_EQ(actualVotes[1].type, vote.type);
     ASSERT_EQ(actualVotes[1].fps, vote.fps);
     ASSERT_EQ(actualVotes[1].seamlessness, vote.seamlessness);
-    ASSERT_EQ(actualVotes[1].category, vote.category);
+    ASSERT_EQ(actualVotes[1].category, FrameRateCategory::Default);
+    ASSERT_TRUE(actualVotes[1].categorySmoothSwitchOnly);
 }
 
 TEST_F(LayerInfoTest, getRefreshRateVote_explicitCategory) {
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index aecfcba..cfc8e99 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -461,8 +461,8 @@
                                                                HAL_PIXEL_FORMAT_RGBA_8888,
                                                                GRALLOC_USAGE_PROTECTED /*usage*/));
     EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
-              ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
-                                                       RequestedLayerState::Changes::Content)
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content)
                       .get());
     mLifecycleManager.commitChanges();
 
@@ -493,10 +493,10 @@
                                                                HAL_PIXEL_FORMAT_RGB_888,
                                                                GRALLOC_USAGE_PROTECTED /*usage*/));
     EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
-              ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
-                                                       RequestedLayerState::Changes::Content |
-                                                       RequestedLayerState::Changes::VisibleRegion |
-                                                       RequestedLayerState::Changes::Visibility)
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content |
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::Visibility)
                       .get());
     mLifecycleManager.commitChanges();
 }
@@ -560,4 +560,36 @@
               ftl::Flags<RequestedLayerState::Changes>().string());
 }
 
+TEST_F(LayerLifecycleManagerTest, layerSecureChangesSetsVisibilityChangeFlag) {
+    // add a default buffer and make the layer secure
+    setFlags(1, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+    setBuffer(1,
+              std::make_shared<renderengine::mock::
+                                       FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                            1ULL /* bufferId */,
+                                                            HAL_PIXEL_FORMAT_RGBA_8888,
+                                                            GRALLOC_USAGE_SW_READ_NEVER /*usage*/));
+
+    mLifecycleManager.commitChanges();
+
+    // set new buffer but layer secure doesn't change
+    setBuffer(1,
+              std::make_shared<renderengine::mock::
+                                       FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                            2ULL /* bufferId */,
+                                                            HAL_PIXEL_FORMAT_RGBA_8888,
+                                                            GRALLOC_USAGE_SW_READ_NEVER /*usage*/));
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content)
+                      .get());
+    mLifecycleManager.commitChanges();
+
+    // change layer flags and confirm visibility flag is set
+    setFlags(1, layer_state_t::eLayerSecure, 0);
+    EXPECT_TRUE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 50cd784..bd0accd 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -17,6 +17,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <common/test/FlagUtils.h>
 #include <renderengine/mock/FakeExternalTexture.h>
 
 #include "FrontEnd/LayerHierarchy.h"
@@ -26,6 +27,8 @@
 #include "LayerHierarchyTest.h"
 #include "ui/GraphicTypes.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 #define UPDATE_AND_VERIFY(BUILDER, ...)                                    \
     ({                                                                     \
         SCOPED_TRACE("");                                                  \
@@ -42,6 +45,7 @@
 
 using ftl::Flags;
 using namespace ftl::flag_operators;
+using namespace com::android::graphics::surfaceflinger;
 
 // To run test:
 /**
@@ -58,13 +62,24 @@
 
     void update(LayerSnapshotBuilder& actualBuilder, LayerSnapshotBuilder::Args& args) {
         if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            mHierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                     mLifecycleManager.getDestroyedLayers());
+            mHierarchyBuilder.update(mLifecycleManager);
         }
         args.root = mHierarchyBuilder.getHierarchy();
         actualBuilder.update(args);
     }
 
+    void update(LayerSnapshotBuilder& actualBuilder) {
+        LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                        .layerLifecycleManager = mLifecycleManager,
+                                        .includeMetadata = false,
+                                        .displays = mFrontEndDisplayInfos,
+                                        .globalShadowSettings = globalShadowSettings,
+                                        .supportsBlur = true,
+                                        .supportedLayerGenericMetadata = {},
+                                        .genericLayerMetadataKeyMap = {}};
+        update(actualBuilder, args);
+    }
+
     void updateAndVerify(LayerSnapshotBuilder& actualBuilder, bool hasDisplayChanges,
                          const std::vector<uint32_t> expectedVisibleLayerIdsInZOrder) {
         LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
@@ -239,7 +254,8 @@
 TEST_F(LayerSnapshotTest, FastPathClearsPreviousChangeStates) {
     setColor(11, {1._hf, 0._hf, 0._hf});
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
-    EXPECT_EQ(getSnapshot(11)->changes, RequestedLayerState::Changes::Content);
+    EXPECT_EQ(getSnapshot(11)->changes,
+              RequestedLayerState::Changes::Content);
     EXPECT_EQ(getSnapshot(11)->clientChanges, layer_state_t::eColorChanged);
     EXPECT_EQ(getSnapshot(1)->changes.get(), 0u);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
@@ -249,7 +265,8 @@
 TEST_F(LayerSnapshotTest, FastPathSetsChangeFlagToContent) {
     setColor(1, {1._hf, 0._hf, 0._hf});
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
-    EXPECT_EQ(getSnapshot(1)->changes, RequestedLayerState::Changes::Content);
+    EXPECT_EQ(getSnapshot(1)->changes,
+              RequestedLayerState::Changes::Content);
     EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eColorChanged);
 }
 
@@ -354,6 +371,23 @@
     EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), modifiedTouchCrop);
 }
 
+TEST_F(LayerSnapshotTest, CanCropTouchableRegionWithDisplayTransform) {
+    DisplayInfo displayInfo;
+    displayInfo.transform = ui::Transform(ui::Transform::RotationFlags::ROT_90, 1000, 1000);
+    mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), displayInfo);
+
+    Rect touchCrop{300, 300, 400, 500};
+    createRootLayer(3);
+    setCrop(3, touchCrop);
+    setLayerStack(3, 1);
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegionCrop(3, touch, /*touchCropId=*/3, /*replaceTouchableRegionWithCrop=*/false);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3});
+    Rect rotatedCrop = {500, 300, 700, 400};
+    EXPECT_EQ(getSnapshot({.id = 3})->inputInfo.touchableRegion.bounds(), rotatedCrop);
+}
+
 TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) {
     int blurRadius = 42;
     setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius));
@@ -640,6 +674,8 @@
 // This test is similar to "frameRate" test case but checks that the setFrameRateCategory API
 // interaction also works correctly with the setFrameRate API within SF frontend.
 TEST_F(LayerSnapshotTest, frameRateWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     // ROOT
     // ├── 1
     // │   ├── 11 (frame rate set to 244.f)
@@ -651,7 +687,7 @@
     // │   └── 13
     // └── 2
     setFrameRate(11, 244.f, 0, 0);
-    setFrameRateCategory(122, 3 /* Normal */);
+    setFrameRateCategory(122, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL);
 
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     // verify parent 1 gets no vote
@@ -836,6 +872,8 @@
 }
 
 TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     // ROOT
     // ├── 1
     // │   ├── 11
@@ -846,7 +884,7 @@
     // │   │       └── 1221
     // │   └── 13
     // └── 2
-    setFrameRateCategory(12, 4 /* high */);
+    setFrameRateCategory(12, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
     setFrameRate(122, 123.f, 0, 0);
     setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
 
@@ -888,7 +926,7 @@
     // │   │       └── 1221
     // │   └── 13
     // └── 2
-    setFrameRateCategory(12, 0 /* default */);
+    setFrameRateCategory(12, ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT);
     setFrameRateSelectionStrategy(12, 0 /* Default */);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     // verify parent 1 gets no vote
@@ -1114,7 +1152,7 @@
 
 TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) {
     hideLayer(1);
-    setTrustedOverlay(1, true);
+    setTrustedOverlay(1, gui::TrustedOverlay::ENABLED);
     Region touch{Rect{0, 0, 1000, 1000}};
     setTouchableRegion(1, touch);
 
@@ -1162,6 +1200,42 @@
     EXPECT_TRUE(getSnapshot(11)->isSecure);
 }
 
+TEST_F(LayerSnapshotTest, setSensitiveForTracingConfigForSecureLayers) {
+    setFlags(11, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+}
+
+TEST_F(LayerSnapshotTest, setSensitiveForTracingFromInputWindowHandle) {
+    setInputInfo(11, [](auto& inputInfo) {
+        inputInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    });
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+}
+
 // b/314350323
 TEST_F(LayerSnapshotTest, propagateDropInputMode) {
     setDropInputMode(1, gui::DropInputMode::ALL);
@@ -1183,4 +1257,161 @@
     EXPECT_EQ(getSnapshot(11)->dropInputMode, gui::DropInputMode::ALL);
 }
 
+TEST_F(LayerSnapshotTest, NonVisibleLayerWithInput) {
+    LayerHierarchyTestBase::createRootLayer(3);
+    setColor(3, {-1._hf, -1._hf, -1._hf});
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
+    transactions.back().states.front().layerId = 3;
+    transactions.back().states.front().state.windowInfoHandle = sp<gui::WindowInfoHandle>::make();
+    auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo();
+    inputInfo->token = sp<BBinder>::make();
+    mLifecycleManager.applyTransactions(transactions);
+
+    update(mSnapshotBuilder);
+
+    bool foundInputLayer = false;
+    mSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        if (snapshot.uniqueSequence == 3) {
+            foundInputLayer = true;
+        }
+    });
+    EXPECT_TRUE(foundInputLayer);
+}
+
+TEST_F(LayerSnapshotTest, canOccludePresentation) {
+    setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation);
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = false,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .displayChanges = false,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_EQ(getSnapshot(1)->inputInfo.canOccludePresentation, false);
+
+    // ensure we can set the property on the window info for layer and all its children
+    EXPECT_EQ(getSnapshot(12)->inputInfo.canOccludePresentation, true);
+    EXPECT_EQ(getSnapshot(121)->inputInfo.canOccludePresentation, true);
+    EXPECT_EQ(getSnapshot(1221)->inputInfo.canOccludePresentation, true);
+}
+
+TEST_F(LayerSnapshotTest, mirroredHierarchyIgnoresLocalTransform) {
+    SET_FLAG_FOR_TEST(flags::detached_mirror, true);
+    reparentLayer(12, UNASSIGNED_LAYER_ID);
+    setPosition(11, 2, 20);
+    setPosition(111, 20, 200);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+    // mirror root has no position set
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->localTransform.tx(), 0);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->localTransform.ty(), 0);
+    // original root still has a position
+    EXPECT_EQ(getSnapshot({.id = 11})->localTransform.tx(), 2);
+    EXPECT_EQ(getSnapshot({.id = 11})->localTransform.ty(), 20);
+
+    // mirror child still has the correct position
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->localTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->localTransform.ty(), 200);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->geomLayerTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->geomLayerTransform.ty(), 200);
+
+    // original child still has the correct position including its parent's position
+    EXPECT_EQ(getSnapshot({.id = 111})->localTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111})->localTransform.ty(), 200);
+    EXPECT_EQ(getSnapshot({.id = 111})->geomLayerTransform.tx(), 22);
+    EXPECT_EQ(getSnapshot({.id = 111})->geomLayerTransform.ty(), 220);
+}
+
+TEST_F(LayerSnapshotTest, overrideParentTrustedOverlayState) {
+    SET_FLAG_FOR_TEST(flags::override_trusted_overlay, true);
+    hideLayer(1);
+    setTrustedOverlay(1, gui::TrustedOverlay::ENABLED);
+
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegion(1, touch);
+    setTouchableRegion(11, touch);
+    setTouchableRegion(111, touch);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+
+    // disable trusted overlay and override parent state
+    setTrustedOverlay(11, gui::TrustedOverlay::DISABLED);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_FALSE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_FALSE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+
+    // unset state and go back to default behavior of inheriting
+    // state
+    setTrustedOverlay(11, gui::TrustedOverlay::UNSET);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+}
+
+TEST_F(LayerSnapshotTest, doNotOverrideParentTrustedOverlayState) {
+    SET_FLAG_FOR_TEST(flags::override_trusted_overlay, false);
+    hideLayer(1);
+    setTrustedOverlay(1, gui::TrustedOverlay::ENABLED);
+
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegion(1, touch);
+    setTouchableRegion(11, touch);
+    setTouchableRegion(111, touch);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+
+    // disable trusted overlay but flag is disabled so this behaves
+    // as UNSET
+    setTrustedOverlay(11, gui::TrustedOverlay::DISABLED);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+
+    // unset state and go back to default behavior of inheriting
+    // state
+    setTrustedOverlay(11, gui::TrustedOverlay::UNSET);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 9aa089f..71f9f88 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -25,6 +25,7 @@
 #include "FrameTimeline.h"
 #include "Scheduler/MessageQueue.h"
 #include "mock/MockVSyncDispatch.h"
+#include "utils/Timers.h"
 
 namespace android {
 
@@ -41,6 +42,7 @@
         return {};
     }
     void sample() override {}
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId) {}
 } gNoOpCompositor;
 
 class TestableMessageQueue : public impl::MessageQueue {
@@ -48,6 +50,8 @@
         using MessageQueue::Handler::Handler;
 
         MOCK_METHOD(void, dispatchFrame, (VsyncId, TimePoint), (override));
+        MOCK_METHOD(bool, isFramePending, (), (const, override));
+        MOCK_METHOD(TimePoint, getExpectedVsyncTime, (), (const override));
     };
 
     explicit TestableMessageQueue(sp<MockHandler> handler)
@@ -72,7 +76,8 @@
 struct MessageQueueTest : testing::Test {
     void SetUp() override {
         EXPECT_CALL(*mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken));
-        EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, kDuration));
+        EXPECT_NO_FATAL_FAILURE(
+                mEventQueue.initVsyncInternal(mVSyncDispatch, mTokenManager, kDuration));
         EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
     }
 
@@ -91,46 +96,62 @@
 TEST_F(MessageQueueTest, commit) {
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
-    EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
+                                                                 .lastVsync = 0};
+    EXPECT_FALSE(mEventQueue.getScheduledFrameResult());
 
-    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+    const auto timePoint = TimePoint::fromNs(1234);
+    const auto scheduleResult = scheduler::ScheduleResult{timePoint, timePoint};
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(scheduleResult));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 
-    ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
-    EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
+    const auto scheduledFrameResult = mEventQueue.getScheduledFrameResult();
+    ASSERT_TRUE(scheduledFrameResult);
+    EXPECT_EQ(1234, scheduledFrameResult->callbackTime.ns());
+    EXPECT_EQ(1234, scheduledFrameResult->vsyncTime.ns());
 }
 
 TEST_F(MessageQueueTest, commitTwice) {
     InSequence s;
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
+                                                                 .lastVsync = 0};
 
-    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+    auto timePoint = TimePoint::fromNs(1234);
+    auto scheduleResult = scheduler::ScheduleResult{timePoint, timePoint};
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(scheduleResult));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 
-    ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
-    EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
+    auto scheduledFrameResult = mEventQueue.getScheduledFrameResult();
+    ASSERT_TRUE(scheduledFrameResult);
+    EXPECT_EQ(1234, scheduledFrameResult->callbackTime.ns());
+    EXPECT_EQ(1234, scheduledFrameResult->vsyncTime.ns());
 
-    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567));
+    timePoint = TimePoint::fromNs(4567);
+    scheduleResult = scheduler::ScheduleResult{timePoint, timePoint};
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(scheduleResult));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 
-    ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
-    EXPECT_EQ(4567, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
+    scheduledFrameResult = mEventQueue.getScheduledFrameResult();
+    ASSERT_TRUE(scheduledFrameResult);
+    EXPECT_EQ(4567, scheduledFrameResult->callbackTime.ns());
+    EXPECT_EQ(4567, scheduledFrameResult->vsyncTime.ns());
 }
 
 TEST_F(MessageQueueTest, commitTwiceWithCallback) {
     InSequence s;
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
+                                                                 .lastVsync = 0};
 
-    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+    const auto timePoint = TimePoint::fromNs(1234);
+    auto scheduleResult = scheduler::ScheduleResult{timePoint, timePoint};
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(scheduleResult));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 
-    ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
-    EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
+    auto scheduledFrameResult = mEventQueue.getScheduledFrameResult();
+    ASSERT_TRUE(scheduledFrameResult);
+    EXPECT_EQ(1234, scheduledFrameResult->callbackTime.ns());
+    EXPECT_EQ(1234, scheduledFrameResult->vsyncTime.ns());
 
     constexpr TimePoint kStartTime = TimePoint::fromNs(100);
     constexpr TimePoint kEndTime = kStartTime + kDuration;
@@ -146,14 +167,15 @@
     EXPECT_NO_FATAL_FAILURE(
             mEventQueue.vsyncCallback(kPresentTime.ns(), kStartTime.ns(), kEndTime.ns()));
 
-    EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
+    EXPECT_FALSE(mEventQueue.getScheduledFrameResult());
 
     const auto timingAfterCallback =
             scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                      .readyDuration = 0,
-                                                     .earliestVsync = kPresentTime.ns()};
-
-    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
+                                                     .lastVsync = kPresentTime.ns()};
+    scheduleResult = scheduler::ScheduleResult{TimePoint::fromNs(0), TimePoint::fromNs(0)};
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback))
+            .WillOnce(Return(scheduleResult));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 }
 
@@ -163,11 +185,26 @@
     const auto timing =
             scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDifferentDuration.ns(),
                                                      .readyDuration = 0,
-                                                     .earliestVsync = 0};
+                                                     .lastVsync = 0};
 
-    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
+    const auto scheduleResult =
+            scheduler::ScheduleResult{TimePoint::fromNs(0), TimePoint::fromNs(0)};
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(scheduleResult));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 }
 
+TEST_F(MessageQueueTest, scheduleResultWhenFrameIsPending) {
+    const auto timePoint = TimePoint::now();
+    EXPECT_CALL(*mEventQueue.mHandler, isFramePending()).WillOnce(Return(true));
+    EXPECT_CALL(*mEventQueue.mHandler, getExpectedVsyncTime()).WillRepeatedly(Return(timePoint));
+
+    const auto scheduledFrameResult = mEventQueue.getScheduledFrameResult();
+
+    ASSERT_TRUE(scheduledFrameResult);
+    EXPECT_NEAR(static_cast<double>(TimePoint::now().ns()),
+                static_cast<double>(scheduledFrameResult->callbackTime.ns()), ms2ns(1));
+    EXPECT_EQ(timePoint, scheduledFrameResult->vsyncTime);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 9c66a97..e74f643 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -18,7 +18,11 @@
 #define LOG_TAG "PowerAdvisorTest"
 
 #include <DisplayHardware/PowerAdvisor.h>
+#include <android_os.h>
 #include <binder/Status.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/FlagManager.h>
+#include <common/test/FlagUtils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <powermanager/PowerHalWrapper.h>
@@ -26,8 +30,8 @@
 #include <chrono>
 #include <future>
 #include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockIPowerHintSession.h"
 #include "mock/DisplayHardware/MockPowerHalController.h"
+#include "mock/DisplayHardware/MockPowerHintSessionWrapper.h"
 
 using namespace android;
 using namespace android::Hwc2::mock;
@@ -41,6 +45,7 @@
 class PowerAdvisorTest : public testing::Test {
 public:
     void SetUp() override;
+    void SetUpFmq(bool usesSharedEventFlag, bool isQueueFull);
     void startPowerHintSession(bool returnValidSession = true);
     void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod);
     void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime);
@@ -49,12 +54,37 @@
     void setTimingTestingMode(bool testinMode);
     void allowReportActualToAcquireMutex();
     bool sessionExists();
+    int64_t toNanos(Duration d);
+
+    struct GpuTestConfig {
+        bool adpfGpuFlagOn;
+        Duration frame1GpuFenceDuration;
+        Duration frame2GpuFenceDuration;
+        Duration vsyncPeriod;
+        Duration presentDuration = 0ms;
+        Duration postCompDuration = 0ms;
+        bool frame1RequiresRenderEngine;
+        bool frame2RequiresRenderEngine;
+        bool usesFmq = false;
+        bool usesSharedFmqFlag = true;
+        bool fmqFull = false;
+    };
+
+    void testGpuScenario(GpuTestConfig& config, WorkDuration& ret);
 
 protected:
     TestableSurfaceFlinger mFlinger;
     std::unique_ptr<PowerAdvisor> mPowerAdvisor;
     MockPowerHalController* mMockPowerHalController;
-    std::shared_ptr<MockIPowerHintSession> mMockPowerHintSession;
+    std::shared_ptr<MockPowerHintSessionWrapper> mMockPowerHintSession;
+    std::shared_ptr<AidlMessageQueue<ChannelMessage, SynchronizedReadWrite>> mBackendFmq;
+    std::shared_ptr<AidlMessageQueue<int8_t, SynchronizedReadWrite>> mBackendFlagQueue;
+    android::hardware::EventFlag* mEventFlag;
+    uint32_t mWriteFlagBitmask = 2;
+    uint32_t mReadFlagBitmask = 1;
+    int64_t mSessionId = 123;
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, true);
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, false);
 };
 
 bool PowerAdvisorTest::sessionExists() {
@@ -62,31 +92,72 @@
     return mPowerAdvisor->mHintSession != nullptr;
 }
 
+int64_t PowerAdvisorTest::toNanos(Duration d) {
+    return std::chrono::nanoseconds(d).count();
+}
+
 void PowerAdvisorTest::SetUp() {
     mPowerAdvisor = std::make_unique<impl::PowerAdvisor>(*mFlinger.flinger());
     mPowerAdvisor->mPowerHal = std::make_unique<NiceMock<MockPowerHalController>>();
     mMockPowerHalController =
             reinterpret_cast<MockPowerHalController*>(mPowerAdvisor->mPowerHal.get());
     ON_CALL(*mMockPowerHalController, getHintSessionPreferredRate)
-            .WillByDefault(Return(HalResult<int64_t>::fromStatus(binder::Status::ok(), 16000)));
+            .WillByDefault(Return(
+                    ByMove(HalResult<int64_t>::fromStatus(ndk::ScopedAStatus::ok(), 16000))));
+}
+
+void PowerAdvisorTest::SetUpFmq(bool usesSharedEventFlag, bool isQueueFull) {
+    mBackendFmq = std::make_shared<
+            AidlMessageQueue<ChannelMessage, SynchronizedReadWrite>>(2, !usesSharedEventFlag);
+    ChannelConfig config;
+    config.channelDescriptor = mBackendFmq->dupeDesc();
+    if (usesSharedEventFlag) {
+        mBackendFlagQueue =
+                std::make_shared<AidlMessageQueue<int8_t, SynchronizedReadWrite>>(1, true);
+        config.eventFlagDescriptor = mBackendFlagQueue->dupeDesc();
+        ASSERT_EQ(android::hardware::EventFlag::createEventFlag(mBackendFlagQueue
+                                                                        ->getEventFlagWord(),
+                                                                &mEventFlag),
+                  android::NO_ERROR);
+    } else {
+        ASSERT_EQ(android::hardware::EventFlag::createEventFlag(mBackendFmq->getEventFlagWord(),
+                                                                &mEventFlag),
+                  android::NO_ERROR);
+    }
+    config.writeFlagBitmask = static_cast<int32_t>(mWriteFlagBitmask);
+    config.readFlagBitmask = static_cast<int32_t>(mReadFlagBitmask);
+    ON_CALL(*mMockPowerHalController, getSessionChannel)
+            .WillByDefault(Return(
+                    ByMove(HalResult<ChannelConfig>::fromStatus(Status::ok(), std::move(config)))));
+    startPowerHintSession();
+    if (isQueueFull) {
+        std::vector<ChannelMessage> msgs;
+        msgs.resize(2);
+        mBackendFmq->writeBlocking(msgs.data(), 2, mReadFlagBitmask, mWriteFlagBitmask,
+                                   std::chrono::nanoseconds(1ms).count(), mEventFlag);
+    }
 }
 
 void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) {
-    mMockPowerHintSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
+    mMockPowerHintSession = std::make_shared<NiceMock<MockPowerHintSessionWrapper>>();
     if (returnValidSession) {
-        ON_CALL(*mMockPowerHalController, createHintSession)
-                .WillByDefault(
-                        Return(HalResult<std::shared_ptr<IPowerHintSession>>::
-                                       fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+        ON_CALL(*mMockPowerHalController, createHintSessionWithConfig)
+                .WillByDefault(DoAll(SetArgPointee<5>(aidl::android::hardware::power::SessionConfig{
+                                             .id = mSessionId}),
+                                     Return(HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                                                    fromStatus(binder::Status::ok(),
+                                                               mMockPowerHintSession))));
     } else {
-        ON_CALL(*mMockPowerHalController, createHintSession)
-                .WillByDefault(Return(HalResult<std::shared_ptr<IPowerHintSession>>::
-                                              fromStatus(binder::Status::ok(), nullptr)));
+        ON_CALL(*mMockPowerHalController, createHintSessionWithConfig).WillByDefault([] {
+            return HalResult<
+                    std::shared_ptr<PowerHintSessionWrapper>>::fromStatus(ndk::ScopedAStatus::ok(),
+                                                                          nullptr);
+        });
     }
     mPowerAdvisor->enablePowerHintSession(true);
     mPowerAdvisor->startPowerHintSession({1, 2, 3});
     ON_CALL(*mMockPowerHintSession, updateTargetWorkDuration)
-            .WillByDefault(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillByDefault(Return(testing::ByMove(HalResult<void>::ok())));
 }
 
 void PowerAdvisorTest::setExpectedTiming(Duration totalFrameTargetDuration,
@@ -109,6 +180,131 @@
     mPowerAdvisor->mDelayReportActualMutexAcquisitonPromise.set_value(true);
 }
 
+void PowerAdvisorTest::testGpuScenario(GpuTestConfig& config, WorkDuration& ret) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_gpu_sf,
+                      config.adpfGpuFlagOn);
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, config.usesFmq);
+    mPowerAdvisor->onBootFinished();
+    bool expectsFmqSuccess = config.usesSharedFmqFlag && !config.fmqFull;
+    if (config.usesFmq) {
+        SetUpFmq(config.usesSharedFmqFlag, config.fmqFull);
+    } else {
+        startPowerHintSession();
+    }
+
+    std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u), GpuVirtualDisplayId(0),
+                                      GpuVirtualDisplayId(1)};
+    mPowerAdvisor->setDisplays(displayIds);
+    auto display1 = displayIds[0];
+    // 60hz
+
+    TimePoint startTime = TimePoint::now();
+    int64_t target;
+    SessionHint hint;
+    if (!config.usesFmq || !expectsFmqSuccess) {
+        EXPECT_CALL(*mMockPowerHintSession, updateTargetWorkDuration(_))
+                .Times(1)
+                .WillOnce(DoAll(testing::SaveArg<0>(&target),
+                                testing::Return(testing::ByMove(HalResult<void>::ok()))));
+        EXPECT_CALL(*mMockPowerHintSession, sendHint(_))
+                .Times(1)
+                .WillOnce(DoAll(testing::SaveArg<0>(&hint),
+                                testing::Return(testing::ByMove(HalResult<void>::ok()))));
+    }
+    // advisor only starts on frame 2 so do an initial frame
+    fakeBasicFrameTiming(startTime, config.vsyncPeriod);
+    // send a load hint
+    mPowerAdvisor->notifyCpuLoadUp();
+    if (config.usesFmq && expectsFmqSuccess) {
+        std::vector<ChannelMessage> msgs;
+        ASSERT_EQ(mBackendFmq->availableToRead(), 2uL);
+        msgs.resize(2);
+        ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 2, mReadFlagBitmask, mWriteFlagBitmask,
+                                              std::chrono::nanoseconds(1ms).count(), mEventFlag));
+        ASSERT_EQ(msgs[0].sessionID, mSessionId);
+        ASSERT_GE(msgs[0].timeStampNanos, startTime.ns());
+        ASSERT_EQ(msgs[0].data.getTag(),
+                  ChannelMessage::ChannelMessageContents::Tag::targetDuration);
+        target = msgs[0].data.get<ChannelMessage::ChannelMessageContents::Tag::targetDuration>();
+        ASSERT_EQ(msgs[1].sessionID, mSessionId);
+        ASSERT_GE(msgs[1].timeStampNanos, startTime.ns());
+        ASSERT_EQ(msgs[1].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::hint);
+        hint = msgs[1].data.get<ChannelMessage::ChannelMessageContents::Tag::hint>();
+    }
+    ASSERT_EQ(target, config.vsyncPeriod.ns());
+    ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
+
+    setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod);
+
+    // report GPU
+    mPowerAdvisor->setRequiresRenderEngine(display1, config.frame1RequiresRenderEngine);
+    if (config.adpfGpuFlagOn) {
+        mPowerAdvisor->setGpuStartTime(display1, startTime);
+    }
+    if (config.frame1GpuFenceDuration.count() == Fence::SIGNAL_TIME_PENDING) {
+        mPowerAdvisor->setGpuFenceTime(display1,
+                                       std::make_unique<FenceTime>(Fence::SIGNAL_TIME_PENDING));
+    } else {
+        TimePoint end = startTime + config.frame1GpuFenceDuration;
+        mPowerAdvisor->setGpuFenceTime(display1, std::make_unique<FenceTime>(end.ns()));
+    }
+
+    // increment the frame
+    std::this_thread::sleep_for(config.vsyncPeriod);
+    startTime = TimePoint::now();
+    fakeBasicFrameTiming(startTime, config.vsyncPeriod);
+    if (config.usesFmq && expectsFmqSuccess) {
+        // same target update will not trigger FMQ write
+        ASSERT_EQ(mBackendFmq->availableToRead(), 0uL);
+    }
+    setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod);
+
+    // report GPU
+    mPowerAdvisor->setRequiresRenderEngine(display1, config.frame2RequiresRenderEngine);
+    if (config.adpfGpuFlagOn) {
+        mPowerAdvisor->setGpuStartTime(display1, startTime);
+    }
+    if (config.frame2GpuFenceDuration.count() == Fence::SIGNAL_TIME_PENDING) {
+        mPowerAdvisor->setGpuFenceTime(display1,
+                                       std::make_unique<FenceTime>(Fence::SIGNAL_TIME_PENDING));
+    } else {
+        TimePoint end = startTime + config.frame2GpuFenceDuration;
+        mPowerAdvisor->setGpuFenceTime(display1, std::make_unique<FenceTime>(end.ns()));
+    }
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + config.presentDuration);
+    mPowerAdvisor->setCompositeEnd(startTime + config.presentDuration + config.postCompDuration);
+
+    // don't report timing for the HWC
+    mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime, startTime);
+    mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime, startTime);
+
+    if (config.usesFmq && expectsFmqSuccess) {
+        mPowerAdvisor->reportActualWorkDuration();
+        ASSERT_EQ(mBackendFmq->availableToRead(), 1uL);
+        std::vector<ChannelMessage> msgs;
+        msgs.resize(1);
+        ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask,
+                                              std::chrono::nanoseconds(1ms).count(), mEventFlag));
+        ASSERT_EQ(msgs[0].sessionID, mSessionId);
+        ASSERT_GE(msgs[0].timeStampNanos, startTime.ns());
+        ASSERT_EQ(msgs[0].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::workDuration);
+        auto actual = msgs[0].data.get<ChannelMessage::ChannelMessageContents::Tag::workDuration>();
+        ret.workPeriodStartTimestampNanos = actual.workPeriodStartTimestampNanos;
+        ret.cpuDurationNanos = actual.cpuDurationNanos;
+        ret.gpuDurationNanos = actual.gpuDurationNanos;
+        ret.durationNanos = actual.durationNanos;
+    } else {
+        std::vector<aidl::android::hardware::power::WorkDuration> durationReq;
+        EXPECT_CALL(*mMockPowerHintSession, reportActualWorkDuration(_))
+                .Times(1)
+                .WillOnce(DoAll(testing::SaveArg<0>(&durationReq),
+                                testing::Return(testing::ByMove(HalResult<void>::ok()))));
+        mPowerAdvisor->reportActualWorkDuration();
+        ASSERT_EQ(durationReq.size(), 1u);
+        ret = std::move(durationReq[0]);
+    }
+}
+
 Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
     return (skipValidate ? PowerAdvisor::kFenceWaitStartDelaySkippedValidate
                          : PowerAdvisor::kFenceWaitStartDelayValidated);
@@ -148,7 +344,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
     mPowerAdvisor->setDisplays(displayIds);
@@ -188,7 +384,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -231,7 +427,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -282,13 +478,6 @@
     mPowerAdvisor->reportActualWorkDuration();
 }
 
-TEST_F(PowerAdvisorTest, hintSessionOnlyCreatedOnce) {
-    EXPECT_CALL(*mMockPowerHalController, createHintSession(_, _, _, _)).Times(1);
-    mPowerAdvisor->onBootFinished();
-    startPowerHintSession();
-    mPowerAdvisor->startPowerHintSession({1, 2, 3});
-}
-
 TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) {
     // notifyDisplayUpdateImminentAndCpuReset or notifyCpuLoadUp gets called in background
     // reportActual gets called during callback and sees true session, passes ensure
@@ -328,17 +517,17 @@
 
     ON_CALL(*mMockPowerHintSession, sendHint).WillByDefault([&letSendHintFinish] {
         letSendHintFinish.get_future().wait();
-        return ndk::ScopedAStatus::fromExceptionCode(-127);
+        return HalResult<void>::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127));
     });
 
     ON_CALL(*mMockPowerHintSession, reportActualWorkDuration).WillByDefault([] {
-        return ndk::ScopedAStatus::fromExceptionCode(-127);
+        return HalResult<void>::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127));
     });
 
-    ON_CALL(*mMockPowerHalController, createHintSession)
-            .WillByDefault(Return(
-                    HalResult<std::shared_ptr<IPowerHintSession>>::
-                            fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr)));
+    ON_CALL(*mMockPowerHalController, createHintSessionWithConfig).WillByDefault([] {
+        return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr);
+    });
 
     // First background call, to notice the session is down
     auto firstHint = std::async(std::launch::async, [this] {
@@ -370,5 +559,284 @@
     EXPECT_EQ(sessionExists(), false);
 }
 
+TEST_F(PowerAdvisorTest, legacyHintSessionCreationStillWorks) {
+    mPowerAdvisor->onBootFinished();
+    mMockPowerHintSession = std::make_shared<NiceMock<MockPowerHintSessionWrapper>>();
+    EXPECT_CALL(*mMockPowerHalController, createHintSessionWithConfig)
+            .Times(1)
+            .WillOnce(Return(HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                                     fromStatus(ndk::ScopedAStatus::fromExceptionCode(
+                                                        EX_UNSUPPORTED_OPERATION),
+                                                nullptr)));
+
+    EXPECT_CALL(*mMockPowerHalController, createHintSession)
+            .Times(1)
+            .WillOnce(Return(HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                                     fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+    mPowerAdvisor->enablePowerHintSession(true);
+    ASSERT_TRUE(mPowerAdvisor->startPowerHintSession({1, 2, 3}));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // faked buffer fence time for testing
+            .frame1GpuFenceDuration = 41ms,
+            .frame2GpuFenceDuration = 31ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = false,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 40ms,
+            .frame2GpuFenceDuration = 30ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = false,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(30ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // faked fence time for testing
+            .frame1GpuFenceDuration = 41ms,
+            .frame2GpuFenceDuration = 31ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = false,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 40ms,
+            .frame2GpuFenceDuration = 30ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = false,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // added a margin as a workaround since we set GPU start time at the time of fence set
+            // call
+            .frame1GpuFenceDuration = 31ms,
+            .frame2GpuFenceDuration = 51ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(50ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(51ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFenceFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = 50ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(50ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(50ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFrame) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            .frame1GpuFenceDuration = 31ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFrame_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 22ms,
+            .postCompDuration = 88ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 22ms,
+            .postCompDuration = 88ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+            .usesFmq = true,
+            .usesSharedFmqFlag = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration_noSharedFlag) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 22ms,
+            .postCompDuration = 88ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+            .usesFmq = true,
+            .usesSharedFmqFlag = false,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration_queueFull) {
+    GpuTestConfig config{.adpfGpuFlagOn = true,
+                         .frame1GpuFenceDuration = 30ms,
+                         .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+                         .vsyncPeriod = 10ms,
+                         .presentDuration = 22ms,
+                         .postCompDuration = 88ms,
+                         .frame1RequiresRenderEngine = true,
+                         .frame2RequiresRenderEngine = true,
+                         .usesFmq = true,
+                         .usesSharedFmqFlag = true,
+                         .fmqFull = true};
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendHint) {
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    mPowerAdvisor->onBootFinished();
+    SetUpFmq(true, false);
+    auto startTime = uptimeNanos();
+    mPowerAdvisor->notifyCpuLoadUp();
+    std::vector<ChannelMessage> msgs;
+    ASSERT_EQ(mBackendFmq->availableToRead(), 1uL);
+    msgs.resize(1);
+    ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask,
+                                          std::chrono::nanoseconds(1ms).count(), mEventFlag));
+    ASSERT_EQ(msgs[0].sessionID, mSessionId);
+    ASSERT_GE(msgs[0].timeStampNanos, startTime);
+    ASSERT_EQ(msgs[0].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::hint);
+    auto hint = msgs[0].data.get<ChannelMessage::ChannelMessageContents::Tag::hint>();
+    ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendHint_noSharedFlag) {
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    mPowerAdvisor->onBootFinished();
+    SetUpFmq(false, false);
+    SessionHint hint;
+    EXPECT_CALL(*mMockPowerHintSession, sendHint(_))
+            .Times(1)
+            .WillOnce(DoAll(testing::SaveArg<0>(&hint),
+                            testing::Return(testing::ByMove(HalResult<void>::ok()))));
+    mPowerAdvisor->notifyCpuLoadUp();
+    ASSERT_EQ(mBackendFmq->availableToRead(), 0uL);
+    ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendHint_queueFull) {
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    mPowerAdvisor->onBootFinished();
+    SetUpFmq(true, true);
+    ASSERT_EQ(mBackendFmq->availableToRead(), 2uL);
+    SessionHint hint;
+    EXPECT_CALL(*mMockPowerHintSession, sendHint(_))
+            .Times(1)
+            .WillOnce(DoAll(testing::SaveArg<0>(&hint),
+                            testing::Return(testing::ByMove(HalResult<void>::ok()))));
+    std::vector<ChannelMessage> msgs;
+    msgs.resize(1);
+    mBackendFmq->writeBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask,
+                               std::chrono::nanoseconds(1ms).count(), mEventFlag);
+    mPowerAdvisor->notifyCpuLoadUp();
+    ASSERT_EQ(mBackendFmq->availableToRead(), 2uL);
+    ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
+}
+
 } // namespace
 } // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 0cacf81..cf9a7d3 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -103,8 +103,9 @@
     auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
 
     auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
-                             GlobalSignals signals) const {
-        const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
+                             GlobalSignals signals = {}, Fps pacesetterFps = {}) const {
+        const auto result =
+                RefreshRateSelector::getRankedFrameRates(layers, signals, pacesetterFps);
 
         EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
                                    ScoredFrameRate::DescendingScore{}));
@@ -114,8 +115,8 @@
 
     auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
                                      GlobalSignals signals) const {
-        const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
-        return std::make_pair(ranking, consideredSignals);
+        const auto result = getRankedFrameRates(layers, signals);
+        return std::make_pair(result.ranking, result.consideredSignals);
     }
 
     FrameRateMode getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
@@ -259,6 +260,50 @@
         config.enableFrameRateOverride = GetParam();
         return TestableRefreshRateSelector(modes, activeModeId, config);
     }
+
+    template <class T>
+    void testFrameRateCategoryWithMultipleLayers(const std::initializer_list<T>& testCases,
+                                                 const TestableRefreshRateSelector& selector) {
+        std::vector<LayerRequirement> layers;
+        for (auto testCase : testCases) {
+            ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+                  to_string(testCase.desiredFrameRate).c_str(),
+                  ftl::enum_string(testCase.frameRateCategory).c_str());
+
+            if (testCase.desiredFrameRate.isValid()) {
+                std::stringstream ss;
+                ss << to_string(testCase.desiredFrameRate)
+                   << ftl::enum_string(testCase.frameRateCategory) << "ExplicitDefault";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = LayerVoteType::ExplicitDefault,
+                                          .desiredRefreshRate = testCase.desiredFrameRate,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            if (testCase.frameRateCategory != FrameRateCategory::Default) {
+                std::stringstream ss;
+                ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = LayerVoteType::ExplicitCategory,
+                                          .frameRateCategory = testCase.frameRateCategory,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            EXPECT_EQ(testCase.expectedFrameRate,
+                      selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
+                    << "Did not get expected frame rate for frameRate="
+                    << to_string(testCase.desiredFrameRate)
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+            EXPECT_EQ(testCase.expectedModeId,
+                      selector.getBestFrameRateMode(layers).modePtr->getId())
+                    << "Did not get expected DisplayModeId for modeId="
+                    << ftl::to_underlying(testCase.expectedModeId)
+                    << " frameRate=" << to_string(testCase.desiredFrameRate)
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+        }
+    }
 };
 
 RefreshRateSelectorTest::RefreshRateSelectorTest() {
@@ -1343,7 +1388,7 @@
 TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
     auto selector = createSelector(kModes_60_90, kModeId60);
 
-    auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
+    auto [refreshRates, signals, _] = selector.getRankedFrameRates({}, {});
     EXPECT_FALSE(signals.powerOnImminent);
 
     auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
@@ -1427,10 +1472,32 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, pacesetterConsidered) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+    constexpr RefreshRateSelector::GlobalSignals kNoSignals;
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].vote = LayerVoteType::Min;
+
+    // The pacesetterFps takes precedence over the LayerRequirement.
+    {
+        const auto result = selector.getRankedFrameRates(layers, {}, 90_Hz);
+        EXPECT_EQ(kMode90, result.ranking.front().frameRateMode.modePtr);
+        EXPECT_EQ(kNoSignals, result.consideredSignals);
+    }
+
+    // The pacesetterFps takes precedence over GlobalSignals.
+    {
+        const auto result = selector.getRankedFrameRates(layers, {.touch = true}, 60_Hz);
+        EXPECT_EQ(kMode60, result.ranking.front().frameRateMode.modePtr);
+        EXPECT_EQ(kNoSignals, result.consideredSignals);
+    }
+}
+
 TEST_P(RefreshRateSelectorTest, touchConsidered) {
     auto selector = createSelector(kModes_60_90, kModeId60);
 
-    auto [_, signals] = selector.getRankedFrameRates({}, {});
+    auto signals = selector.getRankedFrameRates({}, {}).consideredSignals;
     EXPECT_FALSE(signals.touch);
 
     std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
@@ -1496,7 +1563,7 @@
             {0_Hz, FrameRateCategory::High, 90_Hz},
             {0_Hz, FrameRateCategory::Normal, 60_Hz},
             {0_Hz, FrameRateCategory::Low, 30_Hz},
-            {0_Hz, FrameRateCategory::NoPreference, 30_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
 
             // Cases that have both desired frame rate and frame rate category requirements.
             {24_Hz, FrameRateCategory::High, 120_Hz},
@@ -1542,6 +1609,98 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategoryMultiLayers_30_60_90_120) {
+    auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId90;
+    };
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {0_Hz, FrameRateCategory::High, 90_Hz},
+                    {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+                    {0_Hz, FrameRateCategory::Normal, 90_Hz},
+                    {0_Hz, FrameRateCategory::Normal, 90_Hz},
+                    {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+            },
+            selector);
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {0_Hz, FrameRateCategory::Normal, 60_Hz, kModeId60},
+                    {0_Hz, FrameRateCategory::High, 90_Hz},
+                    {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+            },
+            selector);
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {30_Hz, FrameRateCategory::High, 90_Hz},
+                    {24_Hz, FrameRateCategory::High, 120_Hz, kModeId120},
+                    {12_Hz, FrameRateCategory::Normal, 120_Hz, kModeId120},
+                    {30_Hz, FrameRateCategory::NoPreference, 120_Hz, kModeId120},
+
+            },
+            selector);
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {24_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+                    {30_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+                    {120_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+            },
+            selector);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategoryMultiLayers_60_120) {
+    auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId120;
+    };
+
+    testFrameRateCategoryWithMultipleLayers(std::initializer_list<
+                                                    Case>{{0_Hz, FrameRateCategory::High, 120_Hz},
+                                                          {0_Hz, FrameRateCategory::NoPreference,
+                                                           120_Hz},
+                                                          {0_Hz, FrameRateCategory::Normal, 120_Hz},
+                                                          {0_Hz, FrameRateCategory::Normal, 120_Hz},
+                                                          {0_Hz, FrameRateCategory::NoPreference,
+                                                           120_Hz}},
+                                            selector);
+
+    testFrameRateCategoryWithMultipleLayers(std::initializer_list<
+                                                    Case>{{24_Hz, FrameRateCategory::High, 120_Hz},
+                                                          {30_Hz, FrameRateCategory::High, 120_Hz},
+                                                          {12_Hz, FrameRateCategory::Normal,
+                                                           120_Hz},
+                                                          {30_Hz, FrameRateCategory::NoPreference,
+                                                           120_Hz}},
+                                            selector);
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {24_Hz, FrameRateCategory::Default, 120_Hz},
+                    {30_Hz, FrameRateCategory::Default, 120_Hz},
+                    {120_Hz, FrameRateCategory::Default, 120_Hz},
+            },
+            selector);
+}
+
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) {
     auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
 
@@ -1607,6 +1766,356 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_HighHint) {
+    auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    auto actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    // No touch boost, for example a game that uses setFrameRate(30, default compatibility).
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitDefault";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    EXPECT_EQ(30_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId30, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::HighHint;
+    lr2.name = "ExplicitCategory HighHint#2";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::Low;
+    lr2.name = "ExplicitCategory Low";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.frameRateCategory = FrameRateCategory::Default;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitExact;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExact";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    if (selector.supportsAppFrameRateOverrideByContent()) {
+        // Gets touch boost
+        EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+        EXPECT_EQ(kModeId120,
+                  actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+        EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+    } else {
+        EXPECT_EQ(30_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+        EXPECT_EQ(kModeId30, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+        EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+    }
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz Heuristic";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::Min;
+    lr2.name = "Min";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_TouchBoost) {
+    auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    auto actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+    // No touch boost, for example a game that uses setFrameRate(30, default compatibility).
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitDefault";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::HighHint;
+    lr2.name = "ExplicitCategory HighHint";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::Low;
+    lr2.name = "ExplicitCategory Low";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.frameRateCategory = FrameRateCategory::Default;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitExact;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExact";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    if (selector.supportsAppFrameRateOverrideByContent()) {
+        EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz,
+                               actualRankedFrameRates.ranking.front().frameRateMode);
+        EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+    } else {
+        EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz,
+                               actualRankedFrameRates.ranking.front().frameRateMode);
+        EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+    }
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::Min;
+    lr2.name = "Min";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "30Hz Heuristic";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitGte;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitGte";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategory_idleTimer_60_120_nonVrr) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, false);
+    using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
+    struct LayerArg {
+        // Params
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        LayerVoteType voteType = LayerVoteType::ExplicitDefault;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId60;
+    };
+
+    const auto runTest = [&](const TestableRefreshRateSelector& selector,
+                             const std::initializer_list<LayerArg>& layerArgs,
+                             const RefreshRateSelector::GlobalSignals& signals) {
+        std::vector<LayerRequirement> layers;
+        for (auto testCase : layerArgs) {
+            ALOGI("**** %s: Testing frameRateCategory=%s", __func__,
+                  ftl::enum_string(testCase.frameRateCategory).c_str());
+
+            if (testCase.frameRateCategory != FrameRateCategory::Default) {
+                std::stringstream ss;
+                ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = LayerVoteType::ExplicitCategory,
+                                          .frameRateCategory = testCase.frameRateCategory,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            if (testCase.voteType != LayerVoteType::ExplicitDefault) {
+                std::stringstream ss;
+                ss << ftl::enum_string(testCase.voteType);
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = testCase.voteType,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            EXPECT_EQ(testCase.expectedFrameRate,
+                      selector.getBestFrameRateMode(layers, signals).modePtr->getPeakFps())
+                    << "Did not get expected frame rate for"
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+            EXPECT_EQ(testCase.expectedModeId,
+                      selector.getBestFrameRateMode(layers, signals).modePtr->getId())
+                    << "Did not get expected DisplayModeId for modeId="
+                    << ftl::to_underlying(testCase.expectedModeId)
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+        }
+    };
+
+    {
+        // IdleTimer not configured
+        auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120);
+        ASSERT_EQ(0ms, selector.getIdleTimerTimeout());
+
+        runTest(selector,
+                std::initializer_list<LayerArg>{
+                        // Rate does not change due to NoPreference.
+                        {.frameRateCategory = FrameRateCategory::NoPreference,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                        {.voteType = LayerVoteType::NoVote,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                        {.frameRateCategory = FrameRateCategory::NoPreference,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                },
+                {.idle = false});
+    }
+
+    // IdleTimer configured
+    constexpr std::chrono::milliseconds kIdleTimerTimeoutMs = 10ms;
+    auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120,
+                                   Config{
+                                           .legacyIdleTimerTimeout = kIdleTimerTimeoutMs,
+                                   });
+    ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+    ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
+    runTest(selector,
+            std::initializer_list<LayerArg>{
+                    // Rate won't change immediately and will stay 120 due to NoPreference, as
+                    // idle timer did not timeout yet.
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+                    {.voteType = LayerVoteType::NoVote,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+            },
+            {.idle = false});
+
+    // Idle timer is triggered using GlobalSignals.
+    ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+    ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
+    runTest(selector,
+            std::initializer_list<LayerArg>{
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+                    {.voteType = LayerVoteType::NoVote,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+            },
+            {.idle = true});
+}
+
 TEST_P(RefreshRateSelectorTest,
        getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) {
     if (GetParam() != Config::FrameRateOverride::Enabled) {
@@ -1630,8 +2139,7 @@
     const std::initializer_list<Case> testCases = {
             // These layers may switch modes because smoothSwitchOnly=false.
             {FrameRateCategory::Default, false, 120_Hz, kModeId120},
-            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
-            {FrameRateCategory::NoPreference, false, 60_Hz, kModeId60},
+            {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120},
             {FrameRateCategory::Low, false, 30_Hz, kModeId60},
             {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
             {FrameRateCategory::High, false, 120_Hz, kModeId120},
@@ -1639,8 +2147,8 @@
             // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
             // active mode (120Hz).
             {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Normal, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Normal, true, 120_Hz, kModeId120},
             {FrameRateCategory::High, true, 120_Hz, kModeId120},
     };
 
@@ -1878,7 +2386,7 @@
     lr.name = "60Hz ExplicitDefault";
     lr.focused = true;
 
-    const auto [rankedFrameRate, signals] =
+    const auto [rankedFrameRate, signals, _] =
             selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
 
     EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
@@ -2102,7 +2610,7 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
 
-    const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+    const auto [ranking, signals, _] = selector.getRankedFrameRates({}, {});
     EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
     EXPECT_FALSE(signals.touch);
 
@@ -2486,7 +2994,7 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
-        const auto [ranking, signals] =
+        const auto [ranking, signals, _] =
                 selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
@@ -2636,16 +3144,17 @@
     auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     using GlobalSignals = RefreshRateSelector::GlobalSignals;
-    const auto args = std::make_pair(std::vector<LayerRequirement>{},
-                                     GlobalSignals{.touch = true, .idle = true});
-
     const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
                                                                   {90_Hz, kMode90}}},
                                                           GlobalSignals{.touch = true}};
 
-    selector.mutableGetRankedRefreshRatesCache() = {args, result};
+    selector.mutableGetRankedRefreshRatesCache() = {.layers = std::vector<LayerRequirement>{},
+                                                    .signals = GlobalSignals{.touch = true,
+                                                                             .idle = true},
+                                                    .result = result};
 
-    EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
+    const auto& cache = *selector.mutableGetRankedRefreshRatesCache();
+    EXPECT_EQ(result, selector.getRankedFrameRates(cache.layers, cache.signals));
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
@@ -2653,15 +3162,18 @@
 
     EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
 
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+    const std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+    const RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+    const Fps pacesetterFps = 60_Hz;
 
-    const auto result = selector.getRankedFrameRates(layers, globalSignals);
+    const auto result = selector.getRankedFrameRates(layers, globalSignals, pacesetterFps);
 
     const auto& cache = selector.mutableGetRankedRefreshRatesCache();
     ASSERT_TRUE(cache);
 
-    EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+    EXPECT_EQ(cache->layers, layers);
+    EXPECT_EQ(cache->signals, globalSignals);
+    EXPECT_EQ(cache->pacesetterFps, pacesetterFps);
     EXPECT_EQ(cache->result, result);
 }
 
@@ -3073,6 +3585,210 @@
     EXPECT_TRUE(frameRateOverrides.empty());
 }
 
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_withFrameRateCategory) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
+                                            {.ownerUid = 1234, .weight = 1.f}};
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitCategory High";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::High;
+    auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitCategory Normal";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Normal;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitCategory Low";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Low;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitCategory NoPreference";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::NoPreference;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case *without* touch boost has frame rate override.
+    // For example, game and touch interaction.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitDefault 60";
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    layers[1].desiredRefreshRate = 60_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Default;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitExactOrMultiple 30";
+    layers[1].vote = LayerVoteType::ExplicitExactOrMultiple;
+    layers[1].desiredRefreshRate = 30_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Default;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitExact 60";
+    layers[1].vote = LayerVoteType::ExplicitExact;
+    layers[1].desiredRefreshRate = 60_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Default;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitGte 60";
+    layers[1].vote = LayerVoteType::ExplicitGte;
+    layers[1].desiredRefreshRate = 60_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Default;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // ExplicitCategory case that expects no global touch boost and thus has frame rate override.
+    layers[0].name = "ExplicitDefault 60";
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::Default;
+    layers[1].name = "ExplicitCategory High";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::High;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(120_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(120_Hz, frameRateOverrides.at(1234));
+
+    // ExplicitCategory case that expects no global touch boost and thus has frame rate override.
+    layers[0].name = "ExplicitDefault 60";
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::Default;
+    layers[1].name = "ExplicitCategory Normal";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Normal;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    // ExplicitCategory case that expects no global touch boost and thus has frame rate override.
+    layers[0].name = "ExplicitDefault 60";
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::Default;
+    layers[1].name = "ExplicitCategory Low";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Low;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    // ExplicitCategory case that expects no global touch boost and thus has frame rate override.
+    layers[0].name = "ExplicitDefault 60";
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::Default;
+    layers[1].name = "ExplicitCategory NoPreference";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::NoPreference;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+}
+
 TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
     if (GetParam() == Config::FrameRateOverride::Disabled) {
         return;
@@ -3118,6 +3834,17 @@
 
     frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
     EXPECT_TRUE(frameRateOverrides.empty());
+
+    layers[0].vote = LayerVoteType::ExplicitGte;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 }
 
 TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate) {
@@ -3373,7 +4100,7 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
-        const auto [ranking, signals] =
+        const auto [ranking, signals, _] =
                 selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
index e2e3d7b..fba77e9 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -37,7 +37,7 @@
     ~RefreshRateStatsTest();
 
     void resetStats(Fps fps) {
-        mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, fps, PowerMode::OFF);
+        mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, fps);
     }
 
     mock::TimeStats mTimeStats;
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index e515895..4fb0690 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -25,6 +25,7 @@
 #include "Scheduler/EventThread.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "Scheduler/VSyncPredictor.h"
+#include "Scheduler/VSyncReactor.h"
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
@@ -56,6 +57,11 @@
 using LayerHierarchyBuilder = surfaceflinger::frontend::LayerHierarchyBuilder;
 using RequestedLayerState = surfaceflinger::frontend::RequestedLayerState;
 
+class ZeroClock : public Clock {
+public:
+    nsecs_t now() const override { return 0; }
+};
+
 class SchedulerTest : public testing::Test {
 protected:
     class MockEventThreadConnection : public android::EventThreadConnection {
@@ -95,16 +101,12 @@
                                                   kDisplay1Mode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
-    TestableScheduler* mScheduler =
-            new TestableScheduler{mSelector, mSchedulerCallback, mVsyncTrackerCallback};
-    surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
+    TestableSurfaceFlinger mFlinger;
+    TestableScheduler* mScheduler = new TestableScheduler{mSelector, mFlinger, mSchedulerCallback};
+    surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
 
-    ConnectionHandle mConnectionHandle;
     MockEventThread* mEventThread;
     sp<MockEventThreadConnection> mEventThreadConnection;
-
-    TestableSurfaceFlinger mFlinger;
 };
 
 SchedulerTest::SchedulerTest() {
@@ -119,54 +121,13 @@
     EXPECT_CALL(*mEventThread, createEventConnection(_, _))
             .WillRepeatedly(Return(mEventThreadConnection));
 
-    mConnectionHandle = mScheduler->createConnection(std::move(eventThread));
-    EXPECT_TRUE(mConnectionHandle);
+    mScheduler->setEventThread(Cycle::Render, std::move(eventThread));
 
     mFlinger.resetScheduler(mScheduler);
 }
 
 } // namespace
 
-TEST_F(SchedulerTest, invalidConnectionHandle) {
-    ConnectionHandle handle;
-
-    const sp<IDisplayEventConnection> connection = mScheduler->createDisplayEventConnection(handle);
-
-    EXPECT_FALSE(connection);
-    EXPECT_FALSE(mScheduler->getEventConnection(handle));
-
-    // The EXPECT_CALLS make sure we don't call the functions on the subsequent event threads.
-    EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0);
-    mScheduler->onHotplugReceived(handle, kDisplayId1, false);
-
-    std::string output;
-    EXPECT_CALL(*mEventThread, dump(_)).Times(0);
-    mScheduler->dump(handle, output);
-    EXPECT_TRUE(output.empty());
-
-    EXPECT_CALL(*mEventThread, setDuration(10ns, 20ns)).Times(0);
-    mScheduler->setDuration(handle, 10ns, 20ns);
-}
-
-TEST_F(SchedulerTest, validConnectionHandle) {
-    const sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle);
-
-    ASSERT_EQ(mEventThreadConnection, connection);
-    EXPECT_TRUE(mScheduler->getEventConnection(mConnectionHandle));
-
-    EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1);
-    mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false);
-
-    std::string output("dump");
-    EXPECT_CALL(*mEventThread, dump(output)).Times(1);
-    mScheduler->dump(mConnectionHandle, output);
-    EXPECT_FALSE(output.empty());
-
-    EXPECT_CALL(*mEventThread, setDuration(10ns, 20ns)).Times(1);
-    mScheduler->setDuration(mConnectionHandle, 10ns, 20ns);
-}
-
 TEST_F(SchedulerTest, registerDisplay) FTL_FAKE_GUARD(kMainThreadContext) {
     // Hardware VSYNC should not change if the display is already registered.
     EXPECT_CALL(mSchedulerCallback, requestHardwareVsync(kDisplayId1, false)).Times(0);
@@ -238,22 +199,6 @@
     EXPECT_NO_FATAL_FAILURE(mScheduler->dispatchCachedReportedMode());
 }
 
-TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) {
-    const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
-                              .setId(DisplayModeId(111))
-                              .setPhysicalDisplayId(kDisplayId1)
-                              .setVsyncPeriod(111111)
-                              .build();
-
-    // If the handle is incorrect, the function should return before
-    // onModeChange is called.
-    ConnectionHandle invalidHandle = {.id = 123};
-    EXPECT_NO_FATAL_FAILURE(
-            mScheduler->onNonPrimaryDisplayModeChanged(invalidHandle,
-                                                       {90_Hz, ftl::as_non_null(mode)}));
-    EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0);
-}
-
 TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) {
     EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 30ms));
     EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(90_Hz, 30ms));
@@ -342,6 +287,61 @@
     EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals));
 }
 
+TEST_F(SchedulerTest, chooseDisplayModesSingleDisplayHighHintTouchSignal) {
+    mScheduler->registerDisplay(kDisplayId1,
+                                std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+                                                                      kDisplay1Mode60->getId()));
+
+    using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+
+    std::vector<RefreshRateSelector::LayerRequirement> layers =
+            std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    // Scenario that is similar to game. Expects no touch boost.
+    lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitDefault;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitDefault";
+    mScheduler->setContentRequirements(layers);
+    auto modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+    auto choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice({60_Hz, kDisplay1Mode60}, {.touch = false}));
+
+    // Scenario that is similar to video playback and interaction. Expects touch boost.
+    lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    mScheduler->setContentRequirements(layers);
+    modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+    choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, {.touch = true}));
+
+    // Scenario with explicit category and HighHint. Expects touch boost.
+    lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::Low;
+    lr2.name = "ExplicitCategory Low";
+    mScheduler->setContentRequirements(layers);
+    modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+    choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, {.touch = true}));
+}
+
 TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
@@ -350,6 +350,9 @@
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
                                                                       kDisplay2Mode60->getId()));
 
+    mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+    mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON);
+
     using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
     TestableScheduler::DisplayModeChoiceMap expectedChoices;
 
@@ -362,7 +365,7 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{60_Hz,
                                                                                kDisplay2Mode60},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
         std::vector<RefreshRateSelector::LayerRequirement> layers = {{.weight = 1.f},
                                                                      {.weight = 1.f}};
@@ -381,7 +384,7 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{120_Hz,
                                                                                kDisplay2Mode120},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
         mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
@@ -400,7 +403,7 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{120_Hz,
                                                                                kDisplay2Mode120},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -412,6 +415,7 @@
                 ->registerDisplay(kDisplayId3,
                                   std::make_shared<RefreshRateSelector>(kDisplay3Modes,
                                                                         kDisplay3Mode60->getId()));
+        mScheduler->setDisplayPowerMode(kDisplayId3, hal::PowerMode::ON);
 
         const GlobalSignals globalSignals = {.touch = true};
         mScheduler->replaceTouchTimer(10);
@@ -422,10 +426,10 @@
                 DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120},
                                    globalSignals)(kDisplayId2,
                                                   FrameRateMode{120_Hz, kDisplay2Mode120},
-                                                  globalSignals)(kDisplayId3,
-                                                                 FrameRateMode{60_Hz,
-                                                                               kDisplay3Mode60},
-                                                                 globalSignals);
+                                                  GlobalSignals{})(kDisplayId3,
+                                                                   FrameRateMode{60_Hz,
+                                                                                 kDisplay3Mode60},
+                                                                   GlobalSignals{});
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -440,12 +444,12 @@
         expectedChoices = ftl::init::map<
                 const PhysicalDisplayId&,
                 DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60},
-                                   globalSignals)(kDisplayId2,
-                                                  FrameRateMode{60_Hz, kDisplay2Mode60},
-                                                  globalSignals)(kDisplayId3,
-                                                                 FrameRateMode{60_Hz,
-                                                                               kDisplay3Mode60},
-                                                                 globalSignals);
+                                   GlobalSignals{})(kDisplayId2,
+                                                    FrameRateMode{60_Hz, kDisplay2Mode60},
+                                                    GlobalSignals{})(kDisplayId3,
+                                                                     FrameRateMode{60_Hz,
+                                                                                   kDisplay3Mode60},
+                                                                     globalSignals);
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -517,6 +521,7 @@
         }
 
         void sample() override {}
+        void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
     } compositor(*mScheduler);
 
     mScheduler->doFrameSignal(compositor, VsyncId(42));
@@ -568,44 +573,48 @@
                                  hal::VrrConfig{.minFrameIntervalNs = static_cast<int32_t>(
                                                         frameRate.getPeriodNsecs())}));
     std::shared_ptr<VSyncPredictor> vrrTracker =
-            std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction,
-                                             kOutlierTolerancePercent, mVsyncTrackerCallback);
+            std::make_shared<VSyncPredictor>(std::make_unique<ZeroClock>(), kMode, kHistorySize,
+                                             kMinimumSamplesForPrediction,
+                                             kOutlierTolerancePercent);
     std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
             std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId());
     TestableScheduler scheduler{std::make_unique<android::mock::VsyncController>(),
                                 vrrTracker,
                                 vrrSelectorPtr,
-                                sp<VsyncModulator>::make(VsyncConfigSet{}),
-                                mSchedulerCallback,
-                                mVsyncTrackerCallback};
+                                mFlinger.getFactory(),
+                                mFlinger.getTimeStats(),
+                                mSchedulerCallback};
 
     scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
-    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
     vrrTracker->addVsyncTimestamp(0);
-
-    // Next frame at refresh rate as no previous frame
-    EXPECT_EQ(refreshRate,
-              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(0)));
+    // Set 1000 as vsync seq #0
+    vrrTracker->nextAnticipatedVSyncTimeFrom(700);
 
     EXPECT_EQ(Fps::fromPeriodNsecs(1000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(500)));
+                                             TimePoint::fromNs(1000)));
     EXPECT_EQ(Fps::fromPeriodNsecs(1000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(1500)));
+                                             TimePoint::fromNs(2000)));
 
+    // Not crossing the min frame period
+    vrrTracker->onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
+    EXPECT_EQ(Fps::fromPeriodNsecs(1000),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(2500)));
     // Change render rate
     frameRate = Fps::fromPeriodNsecs(2000);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
-    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
 
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(2500)));
+                                             TimePoint::fromNs(5500)));
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(4500)));
+                                             TimePoint::fromNs(7500)));
 }
 
 TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) {
@@ -700,7 +709,7 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
     const sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle());
 
     EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers().size());
     ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
@@ -729,9 +738,9 @@
             .WillOnce(Return(mockConnection2));
 
     const sp<IDisplayEventConnection> connection1 =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, handle);
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, handle);
     const sp<IDisplayEventConnection> connection2 =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, handle);
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, handle);
 
     EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers().size());
     ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
@@ -749,9 +758,9 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached).Times(2);
     const sp<IDisplayEventConnection> connection1 =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer1->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer1->getHandle());
     const sp<IDisplayEventConnection> connection2 =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer2->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer2->getHandle());
 
     EXPECT_EQ(2u, mScheduler->mutableAttachedChoreographers().size());
 
@@ -778,7 +787,7 @@
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
 
     sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle());
 
     ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
     EXPECT_EQ(1u,
@@ -808,7 +817,7 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
     const sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle());
 
     layer.clear();
     mFlinger.mutableLayersPendingRemoval().clear();
@@ -822,7 +831,7 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
     sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle());
 
     RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
     LayerHierarchy hierarchy(&layerState);
@@ -882,7 +891,7 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
     sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, parent->getHandle());
 
     RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
     LayerHierarchy parentHierarchy(&parentState);
@@ -909,7 +918,7 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
     sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, parent->getHandle());
 
     RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
     LayerHierarchy parentHierarchy(&parentState);
@@ -944,7 +953,7 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
     sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, parent->getHandle());
 
     RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
     LayerHierarchy parentHierarchy(&parentState);
@@ -978,7 +987,7 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
     sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle());
 
     RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
     LayerHierarchy parentHierarchy(&parentState);
@@ -1004,7 +1013,7 @@
 
     EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
     sp<IDisplayEventConnection> connection =
-            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+            mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle());
 
     RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
     LayerHierarchy parentHierarchy(&parentState);
diff --git a/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp b/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp
deleted file mode 100644
index 45b7610..0000000
--- a/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include "Scheduler/StrongTyping.h"
-
-using namespace testing;
-
-namespace android {
-
-TEST(StrongTypeTest, comparison) {
-    using SpunkyType = StrongTyping<int, struct SpunkyTypeTag, Compare>;
-    SpunkyType f1(10);
-
-    EXPECT_TRUE(f1 == f1);
-    EXPECT_TRUE(SpunkyType(10) != SpunkyType(11));
-    EXPECT_FALSE(SpunkyType(31) != SpunkyType(31));
-
-    EXPECT_TRUE(SpunkyType(10) < SpunkyType(11));
-    EXPECT_TRUE(SpunkyType(-1) < SpunkyType(0));
-    EXPECT_FALSE(SpunkyType(-10) < SpunkyType(-20));
-
-    EXPECT_TRUE(SpunkyType(10) <= SpunkyType(11));
-    EXPECT_TRUE(SpunkyType(10) <= SpunkyType(10));
-    EXPECT_TRUE(SpunkyType(-10) <= SpunkyType(1));
-    EXPECT_FALSE(SpunkyType(10) <= SpunkyType(9));
-
-    EXPECT_TRUE(SpunkyType(11) >= SpunkyType(11));
-    EXPECT_TRUE(SpunkyType(12) >= SpunkyType(11));
-    EXPECT_FALSE(SpunkyType(11) >= SpunkyType(12));
-
-    EXPECT_FALSE(SpunkyType(11) > SpunkyType(12));
-    EXPECT_TRUE(SpunkyType(-11) < SpunkyType(7));
-}
-
-TEST(StrongTypeTest, addition) {
-    using FunkyType = StrongTyping<int, struct FunkyTypeTag, Compare, Add>;
-    FunkyType f2(22);
-    FunkyType f1(10);
-
-    EXPECT_THAT(f1 + f2, Eq(FunkyType(32)));
-    EXPECT_THAT(f2 + f1, Eq(FunkyType(32)));
-
-    EXPECT_THAT(++f1.value(), Eq(11));
-    EXPECT_THAT(f1.value(), Eq(11));
-    EXPECT_THAT(f1++.value(), Eq(11));
-    EXPECT_THAT(f1++.value(), Eq(12));
-    EXPECT_THAT(f1.value(), Eq(13));
-
-    auto f3 = f1;
-    EXPECT_THAT(f1, Eq(f3));
-    EXPECT_THAT(f1, Lt(f2));
-
-    f3 += f1;
-    EXPECT_THAT(f1.value(), Eq(13));
-    EXPECT_THAT(f3.value(), Eq(26));
-}
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
new file mode 100644
index 0000000..ff7612e
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "CommitAndCompositeTest.h"
+
+#define EXPECT_COLOR_MATRIX_CHANGED(current, drawing)               \
+    EXPECT_EQ(current, mFlinger.currentState().colorMatrixChanged); \
+    EXPECT_EQ(drawing, mFlinger.drawingState().colorMatrixChanged);
+
+namespace android {
+
+class ColorMatrixTest : public CommitAndCompositeTest {};
+
+TEST_F(ColorMatrixTest, colorMatrixChanged) {
+    mFlinger.enableLayerLifecycleManager();
+    EXPECT_COLOR_MATRIX_CHANGED(true, true);
+    mFlinger.mutableTransactionFlags() |= eTransactionNeeded;
+
+    mFlinger.commitAndComposite();
+    EXPECT_COLOR_MATRIX_CHANGED(false, false);
+
+    mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly);
+    EXPECT_COLOR_MATRIX_CHANGED(true, false);
+
+    mFlinger.commit();
+    EXPECT_COLOR_MATRIX_CHANGED(false, true);
+
+    mFlinger.commitAndComposite();
+    EXPECT_COLOR_MATRIX_CHANGED(false, false);
+}
+
+TEST_F(ColorMatrixTest, colorMatrixChangedAfterDisplayTransaction) {
+    mFlinger.enableLayerLifecycleManager();
+    EXPECT_COLOR_MATRIX_CHANGED(true, true);
+    mFlinger.mutableTransactionFlags() |= eTransactionNeeded;
+
+    mFlinger.commitAndComposite();
+    EXPECT_COLOR_MATRIX_CHANGED(false, false);
+
+    static const std::string kDisplayName("Test Display");
+    mFlinger.createVirtualDisplay(kDisplayName, false /*isSecure=*/);
+
+    mFlinger.commit();
+    EXPECT_COLOR_MATRIX_CHANGED(false, true);
+
+    mFlinger.commitAndComposite();
+    EXPECT_COLOR_MATRIX_CHANGED(false, false);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index 28162f4..e5f2a91 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -27,7 +27,7 @@
 
 class CreateDisplayTest : public DisplayTransactionTest {
 public:
-    void createDisplayWithRequestedRefreshRate(const String8& name, uint64_t displayId,
+    void createDisplayWithRequestedRefreshRate(const std::string& name, uint64_t displayId,
                                                float pacesetterDisplayRefreshRate,
                                                float requestedRefreshRate,
                                                float expectedAdjustedRefreshRate) {
@@ -37,7 +37,7 @@
         // --------------------------------------------------------------------
         // Invocation
 
-        sp<IBinder> displayToken = mFlinger.createDisplay(name, false, requestedRefreshRate);
+        sp<IBinder> displayToken = mFlinger.createVirtualDisplay(name, false, requestedRefreshRate);
 
         // --------------------------------------------------------------------
         // Postconditions
@@ -73,7 +73,7 @@
 };
 
 TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) {
-    const String8 name("virtual.test");
+    static const std::string name("virtual.test");
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -81,7 +81,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    sp<IBinder> displayToken = mFlinger.createDisplay(name, false);
+    sp<IBinder> displayToken = mFlinger.createVirtualDisplay(name, false);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -101,7 +101,7 @@
 }
 
 TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) {
-    const String8 name("virtual.test");
+    static const std::string kDisplayName("virtual.test");
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -112,7 +112,7 @@
     // Set the calling identity to graphics so captureDisplay with secure is allowed.
     IPCThreadState::self()->restoreCallingIdentity(static_cast<int64_t>(AID_GRAPHICS) << 32 |
                                                    AID_GRAPHICS);
-    sp<IBinder> displayToken = mFlinger.createDisplay(name, true);
+    sp<IBinder> displayToken = mFlinger.createVirtualDisplay(kDisplayName, true);
     IPCThreadState::self()->restoreCallingIdentity(oldId);
 
     // --------------------------------------------------------------------
@@ -123,7 +123,37 @@
     const auto& display = getCurrentDisplayState(displayToken);
     EXPECT_TRUE(display.isVirtual());
     EXPECT_TRUE(display.isSecure);
-    EXPECT_EQ(name.c_str(), display.displayName);
+    EXPECT_EQ(kDisplayName.c_str(), display.displayName);
+
+    // --------------------------------------------------------------------
+    // Cleanup conditions
+
+    // Creating the display commits a display transaction.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+}
+
+TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForUniqueId) {
+    static const std::string kDisplayName("virtual.test");
+    static const std::string kUniqueId = "virtual:package:id";
+
+    // --------------------------------------------------------------------
+    // Call Expectations
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    sp<IBinder> displayToken = mFlinger.createVirtualDisplay(kDisplayName, false, kUniqueId);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The display should have been added to the current state
+    ASSERT_TRUE(hasCurrentDisplayState(displayToken));
+    const auto& display = getCurrentDisplayState(displayToken);
+    EXPECT_TRUE(display.isVirtual());
+    EXPECT_FALSE(display.isSecure);
+    EXPECT_EQ(display.uniqueId, "virtual:package:id");
+    EXPECT_EQ(kDisplayName.c_str(), display.displayName);
 
     // --------------------------------------------------------------------
     // Cleanup conditions
@@ -134,78 +164,78 @@
 
 // Requesting 0 tells SF not to do anything, i.e., default to refresh as physical displays
 TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRate0) {
-    const String8 displayName("virtual.test");
-    const uint64_t displayId = 123ull;
-    const float kPacesetterDisplayRefreshRate = 60.f;
-    const float kRequestedRefreshRate = 0.f;
-    const float kExpectedAdjustedRefreshRate = 0.f;
-    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = 0.f;
+    constexpr float kExpectedAdjustedRefreshRate = 0.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate,
                                           kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
 }
 
 // Requesting negative refresh rate, will be ignored, same as requesting 0
 TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNegative) {
-    const String8 displayName("virtual.test");
-    const uint64_t displayId = 123ull;
-    const float kPacesetterDisplayRefreshRate = 60.f;
-    const float kRequestedRefreshRate = -60.f;
-    const float kExpectedAdjustedRefreshRate = 0.f;
-    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = -60.f;
+    constexpr float kExpectedAdjustedRefreshRate = 0.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate,
                                           kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
 }
 
 // Requesting a higher refresh rate than the pacesetter
 TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateHigh) {
-    const String8 displayName("virtual.test");
-    const uint64_t displayId = 123ull;
-    const float kPacesetterDisplayRefreshRate = 60.f;
-    const float kRequestedRefreshRate = 90.f;
-    const float kExpectedAdjustedRefreshRate = 60.f;
-    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = 90.f;
+    constexpr float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate,
                                           kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
 }
 
 // Requesting the same refresh rate as the pacesetter
 TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateSame) {
-    const String8 displayName("virtual.test");
-    const uint64_t displayId = 123ull;
-    const float kPacesetterDisplayRefreshRate = 60.f;
-    const float kRequestedRefreshRate = 60.f;
-    const float kExpectedAdjustedRefreshRate = 60.f;
-    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = 60.f;
+    constexpr float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate,
                                           kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
 }
 
 // Requesting a divisor (30) of the pacesetter (60) should be honored
 TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateDivisor) {
-    const String8 displayName("virtual.test");
-    const uint64_t displayId = 123ull;
-    const float kPacesetterDisplayRefreshRate = 60.f;
-    const float kRequestedRefreshRate = 30.f;
-    const float kExpectedAdjustedRefreshRate = 30.f;
-    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = 30.f;
+    constexpr float kExpectedAdjustedRefreshRate = 30.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate,
                                           kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
 }
 
 // Requesting a non divisor (45) of the pacesetter (120) should round up to a divisor (60)
 TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisor) {
-    const String8 displayName("virtual.test");
-    const uint64_t displayId = 123ull;
-    const float kPacesetterDisplayRefreshRate = 120.f;
-    const float kRequestedRefreshRate = 45.f;
-    const float kExpectedAdjustedRefreshRate = 60.f;
-    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 120.f;
+    constexpr float kRequestedRefreshRate = 45.f;
+    constexpr float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate,
                                           kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
 }
 
 // Requesting a non divisor (75) of the pacesetter (120) should round up to pacesetter (120)
 TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisorMax) {
-    const String8 displayName("virtual.test");
-    const uint64_t displayId = 123ull;
-    const float kPacesetterDisplayRefreshRate = 120.f;
-    const float kRequestedRefreshRate = 75.f;
-    const float kExpectedAdjustedRefreshRate = 120.f;
-    createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 120.f;
+    constexpr float kRequestedRefreshRate = 75.f;
+    constexpr float kExpectedAdjustedRefreshRate = 120.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate,
                                           kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
index 93a3811..f8ad8e1 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
@@ -43,18 +43,18 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.destroyDisplay(existing.token());
+    EXPECT_EQ(NO_ERROR, mFlinger.destroyVirtualDisplay(existing.token()));
 
     // --------------------------------------------------------------------
     // Postconditions
 
-    // The display should have been removed from the current state
+    // The display should have been removed from the current state.
     EXPECT_FALSE(hasCurrentDisplayState(existing.token()));
 
-    // Ths display should still exist in the drawing state
+    // Ths display should still exist in the drawing state.
     EXPECT_TRUE(hasDrawingDisplayState(existing.token()));
 
-    // The display transaction needed flasg should be set
+    // The display transaction needed flags should be set.
     EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
 }
 
@@ -67,7 +67,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.destroyDisplay(displayToken);
+    EXPECT_EQ(NAME_NOT_FOUND, mFlinger.destroyVirtualDisplay(displayToken));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 7ad97a2..078b4fe 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -21,9 +21,21 @@
 #include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockDisplayModeSpecs.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
 #include <ftl/fake_guard.h>
 #include <scheduler/Fps.h>
 
+using namespace com::android::graphics::surfaceflinger;
+
+#define EXPECT_SET_ACTIVE_CONFIG(displayId, modeId)                                 \
+    EXPECT_CALL(*mComposer,                                                         \
+                setActiveConfigWithConstraints(displayId,                           \
+                                               static_cast<hal::HWConfigId>(        \
+                                                       ftl::to_underlying(modeId)), \
+                                               _, _))                               \
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)))
+
 namespace android {
 namespace {
 
@@ -53,7 +65,7 @@
         auto vsyncController = std::make_unique<mock::VsyncController>();
         auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         EXPECT_CALL(*vsyncTracker, currentPeriod())
                 .WillRepeatedly(Return(
                         TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
@@ -138,14 +150,14 @@
     auto vsyncController = std::make_unique<mock::VsyncController>();
     auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
     EXPECT_CALL(*vsyncTracker, currentPeriod())
             .WillRepeatedly(
                     Return(TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
     EXPECT_CALL(*vsyncTracker, minFramePeriod())
             .WillRepeatedly(Return(Period::fromNs(
                     TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
     mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
                             std::move(eventThread), std::move(sfEventThread),
                             std::move(selectorPtr),
@@ -161,8 +173,7 @@
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
-                                                                     120));
+                                        mock::createDisplayModeSpecs(kModeId90, false, 0, 120));
 
     ASSERT_TRUE(mDisplay->getDesiredMode());
     EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90);
@@ -170,10 +181,7 @@
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                               hal::HWConfigId(kModeId90.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
 
     mFlinger.commit();
 
@@ -202,8 +210,7 @@
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90.value(), true, 0,
-                                                                     120));
+                                        mock::createDisplayModeSpecs(kModeId90, true, 0, 120));
 
     ASSERT_TRUE(mDisplay->getDesiredMode());
     EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90);
@@ -212,10 +219,7 @@
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                               hal::HWConfigId(kModeId90.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
 
     EXPECT_CALL(*mAppEventThread,
                 onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
@@ -238,28 +242,20 @@
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
-                                                                     120));
+                                        mock::createDisplayModeSpecs(kModeId90, false, 0, 120));
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                               hal::HWConfigId(kModeId90.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
 
     mFlinger.commit();
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId120.value(), false, 0,
-                                                                     180));
+                                        mock::createDisplayModeSpecs(kModeId120, false, 0, 180));
 
     ASSERT_TRUE(mDisplay->getDesiredMode());
     EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120);
 
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                               hal::HWConfigId(kModeId120.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId120);
 
     mFlinger.commit();
 
@@ -281,8 +277,7 @@
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0,
-                                                                     120));
+                                        mock::createDisplayModeSpecs(kModeId90_4K, false, 0, 120));
 
     ASSERT_TRUE(mDisplay->getDesiredMode());
     EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90_4K);
@@ -291,10 +286,7 @@
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                               hal::HWConfigId(kModeId90_4K.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90_4K);
 
     EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplay->getPhysicalId(), true));
 
@@ -331,11 +323,13 @@
     }
 
     if (arg->getDesiredMode()->mode.modePtr->getId() != modeId) {
-        *result_listener << "Unexpected desired mode " << modeId;
+        *result_listener << "Unexpected desired mode " << ftl::to_underlying(modeId);
         return false;
     }
 
-    if (!flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
+    // VsyncModulator should react to mode switches on the pacesetter display.
+    if (arg->getPhysicalId() == flinger->scheduler()->pacesetterDisplayId() &&
+        !flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
         *result_listener << "VsyncModulator did not shift to early phase";
         return false;
     }
@@ -345,14 +339,15 @@
 
 MATCHER_P(ModeSettledTo, modeId, "") {
     if (const auto desiredOpt = arg->getDesiredMode()) {
-        *result_listener << "Unsettled desired mode " << desiredOpt->mode.modePtr->getId();
+        *result_listener << "Unsettled desired mode "
+                         << ftl::to_underlying(desiredOpt->mode.modePtr->getId());
         return false;
     }
 
     ftl::FakeGuard guard(kMainThreadContext);
 
     if (arg->getActiveMode().modePtr->getId() != modeId) {
-        *result_listener << "Settled to unexpected active mode " << modeId;
+        *result_listener << "Settled to unexpected active mode " << ftl::to_underlying(modeId);
         return false;
     }
 
@@ -360,6 +355,13 @@
 }
 
 TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) {
+    SET_FLAG_FOR_TEST(flags::connected_display, true);
+
+    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
+    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
+            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
+                            Return(hal::V2_4::Error::NONE)));
+
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -368,67 +370,71 @@
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
-    // Only the inner display is powered on.
-    mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
 
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90.value(),
-                                                                               false, 0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, false,
+                                                                               0.f, 120.f)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60.value(),
-                                                                               false, 0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, false,
+                                                                               0.f, 120.f)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(kInnerDisplayHwcId,
-                                               hal::HWConfigId(kModeId90.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
-
-    mFlinger.commit();
-
-    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    mFlinger.commit();
-
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    innerDisplay->setPowerMode(hal::PowerMode::OFF);
-    outerDisplay->setPowerMode(hal::PowerMode::ON);
-
-    // Only the outer display is powered on.
-    mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay);
-
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(kOuterDisplayHwcId,
-                                               hal::HWConfigId(kModeId60.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60);
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     mFlinger.commit();
 
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId60, false,
+                                                                               0.f, 120.f)));
+
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId60);
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
 }
 
 TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) {
+    SET_FLAG_FOR_TEST(flags::connected_display, true);
+
+    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
+    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
+            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
+                            Return(hal::V2_4::Error::NONE)));
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -437,37 +443,28 @@
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
-    outerDisplay->setPowerMode(hal::PowerMode::ON);
-
-    // Both displays are powered on.
-    mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
 
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90.value(),
-                                                                               false, 0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, false,
+                                                                               0.f, 120.f)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60.value(),
-                                                                               false, 0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, false,
+                                                                               0.f, 120.f)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(kInnerDisplayHwcId,
-                                               hal::HWConfigId(kModeId90.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
-
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(kOuterDisplayHwcId,
-                                               hal::HWConfigId(kModeId60.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60);
 
     mFlinger.commit();
 
@@ -486,19 +483,16 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90.value(),
-                                                                               false, 0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, false,
+                                                                               0.f, 120.f)));
 
     EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
     // Power off the display before the mode has been set.
-    mDisplay->setPowerMode(hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(mDisplay, hal::PowerMode::OFF);
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(kInnerDisplayHwcId,
-                                               hal::HWConfigId(kModeId90.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
 
     mFlinger.commit();
 
@@ -512,6 +506,13 @@
 }
 
 TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) {
+    SET_FLAG_FOR_TEST(flags::connected_display, true);
+
+    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
+    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
+            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
+                            Return(hal::V2_4::Error::NONE)));
+
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -520,68 +521,62 @@
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
-    outerDisplay->setPowerMode(hal::PowerMode::ON);
-
-    // Both displays are powered on.
-    mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
 
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90.value(),
-                                                                               false, 0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, false,
+                                                                               0.f, 120.f)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60.value(),
-                                                                               false, 0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, false,
+                                                                               0.f, 120.f)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     // Power off the outer display before the mode has been set.
-    outerDisplay->setPowerMode(hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::OFF);
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(kInnerDisplayHwcId,
-                                               hal::HWConfigId(kModeId90.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60);
 
     mFlinger.commit();
 
-    // Powering off the inactive display should abort the mode set.
+    // Powering off the inactive display should not abort the mode set.
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    mFlinger.commit();
-
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    innerDisplay->setPowerMode(hal::PowerMode::OFF);
-    outerDisplay->setPowerMode(hal::PowerMode::ON);
-
-    // Only the outer display is powered on.
-    mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay);
-
-    EXPECT_CALL(*mComposer,
-                setActiveConfigWithConstraints(kOuterDisplayHwcId,
-                                               hal::HWConfigId(kModeId60.value()), _, _))
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
-
-    mFlinger.commit();
-
-    // The mode set should resume once the display becomes active.
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     mFlinger.commit();
 
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId120, false,
+                                                                               0.f, 120.f)));
+
+    EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId120);
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId120));
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
index 41d8f9e..19f8deb 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
@@ -17,9 +17,9 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
-#include "DisplayTransactionTestHelpers.h"
 #include <com_android_graphics_surfaceflinger_flags.h>
 #include <common/test/FlagUtils.h>
+#include "DualDisplayTransactionTest.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -29,35 +29,9 @@
 namespace android {
 namespace {
 
-struct FoldableTest : DisplayTransactionTest {
-    static constexpr bool kWithMockScheduler = false;
-    FoldableTest() : DisplayTransactionTest(kWithMockScheduler) {}
-
-    void SetUp() override {
-        injectMockScheduler(kInnerDisplayId);
-
-        // Inject inner and outer displays with uninitialized power modes.
-        constexpr bool kInitPowerMode = false;
-        {
-            InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
-            auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
-            injector.setPowerMode(std::nullopt);
-            injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
-            mInnerDisplay = injector.inject();
-        }
-        {
-            OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
-            auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
-            injector.setPowerMode(std::nullopt);
-            mOuterDisplay = injector.inject();
-        }
-    }
-
-    static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
-    static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
-
-    sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
-};
+constexpr bool kExpectSetPowerModeOnce = false;
+struct FoldableTest : DualDisplayTransactionTest<hal::PowerMode::OFF, hal::PowerMode::OFF,
+                                                 kExpectSetPowerModeOnce> {};
 
 TEST_F(FoldableTest, promotesPacesetterOnBoot) {
     // When the device boots, the inner display should be the pacesetter.
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
index 29acfaa..f424133 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
@@ -17,79 +17,16 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlingerGetDisplayStatsTest"
 
-#include <compositionengine/Display.h>
-#include <compositionengine/mock/DisplaySurface.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <renderengine/mock/RenderEngine.h>
 #include <ui/DisplayStatInfo.h>
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/DisplayHardware/MockPowerAdvisor.h"
-#include "mock/MockTimeStats.h"
-#include "mock/system/window/MockNativeWindow.h"
 
-using namespace android;
-using namespace testing;
+#include "CommitAndCompositeTest.h"
 
 namespace android {
 namespace {
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 
-constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
-constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
-constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
-constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
-
-class SurfaceFlingerGetDisplayStatsTest : public Test {
-public:
-    void SetUp() override;
-
-protected:
-    TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
-    sp<DisplayDevice> mDisplay;
-    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
-            sp<compositionengine::mock::DisplaySurface>::make();
-    sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
-    mock::TimeStats* mTimeStats = new mock::TimeStats();
-    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
-    Hwc2::mock::Composer* mComposer = nullptr;
-};
-
-void SurfaceFlingerGetDisplayStatsTest::SetUp() {
-    mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
-    mComposer = new Hwc2::mock::Composer();
-    mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
-    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
-    mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
-    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
-    mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
-    static constexpr bool kIsPrimary = true;
-    FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
-            .setPowerMode(hal::PowerMode::ON)
-            .inject(&mFlinger, mComposer);
-    auto compostionEngineDisplayArgs =
-            compositionengine::DisplayCreationArgsBuilder()
-                    .setId(DEFAULT_DISPLAY_ID)
-                    .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
-                    .setPowerAdvisor(mPowerAdvisor)
-                    .setName("injected display")
-                    .build();
-    auto compositionDisplay =
-            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
-                                                   std::move(compostionEngineDisplayArgs));
-    mDisplay =
-            FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
-                                      ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary)
-                    .setDisplaySurface(mDisplaySurface)
-                    .setNativeWindow(mNativeWindow)
-                    .setPowerMode(hal::PowerMode::ON)
-                    .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
-                    .skipRegisterDisplay()
-                    .inject();
-}
+struct SurfaceFlingerGetDisplayStatsTest : CommitAndCompositeTest {};
 
 // TODO (b/277364366): Clients should be updated to pass in the display they want.
 TEST_F(SurfaceFlingerGetDisplayStatsTest, nullptrSucceeds) {
@@ -105,8 +42,8 @@
 }
 
 TEST_F(SurfaceFlingerGetDisplayStatsTest, invalidToken) {
-    const String8 displayName("fakeDisplay");
-    sp<IBinder> displayToken = mFlinger.createDisplay(displayName, false);
+    static const std::string kDisplayName("fakeDisplay");
+    sp<IBinder> displayToken = mFlinger.createVirtualDisplay(kDisplayName, false /*isSecure*/);
     DisplayStatInfo info;
     status_t status = mFlinger.getDisplayStats(displayToken, &info);
     EXPECT_EQ(status, NAME_NOT_FOUND);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index a270dc9..897f9a0 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -17,8 +17,14 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
 #include "DisplayTransactionTestHelpers.h"
 
+using namespace com::android::graphics::surfaceflinger;
+using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent;
+
 namespace android {
 
 class HotplugTest : public DisplayTransactionTest {};
@@ -87,6 +93,8 @@
 }
 
 TEST_F(HotplugTest, rejectsHotplugIfFailedToLoadDisplayModes) {
+    SET_FLAG_FOR_TEST(flags::connected_display, true);
+
     // Inject a primary display.
     PrimaryDisplayVariant::injectHwcDisplay(this);
 
@@ -94,6 +102,10 @@
     constexpr bool kFailedHotplug = true;
     ExternalDisplay::setupHwcHotplugCallExpectations<kFailedHotplug>(this);
 
+    EXPECT_CALL(*mEventThread,
+                onHotplugConnectionError(static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN)))
+            .Times(1);
+
     // Simulate a connect event that fails to load display modes due to HWC already having
     // disconnected the display but SF yet having to process the queued disconnect event.
     EXPECT_CALL(*mComposer, getActiveConfig(ExternalDisplay::HWC_DISPLAY_ID, _))
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index fc5f2b0..eaf4684 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -17,66 +17,49 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
-#include "DisplayTransactionTestHelpers.h"
+#include "DualDisplayTransactionTest.h"
 
 namespace android {
 namespace {
 
-class InitializeDisplaysTest : public DisplayTransactionTest {};
+constexpr bool kExpectSetPowerModeOnce = false;
+struct InitializeDisplaysTest : DualDisplayTransactionTest<hal::PowerMode::OFF, hal::PowerMode::OFF,
+                                                           kExpectSetPowerModeOnce> {};
 
-TEST_F(InitializeDisplaysTest, commitsPrimaryDisplay) {
-    using Case = SimplePrimaryDisplayCase;
-
-    // --------------------------------------------------------------------
-    // Preconditions
-
-    // A primary display is set up
-    Case::Display::injectHwcDisplay(this);
-    auto primaryDisplay = Case::Display::makeFakeExistingDisplayInjector(this);
-    primaryDisplay.inject();
-
-    // --------------------------------------------------------------------
-    // Call Expectations
-
-    // We expect a call to get the active display config.
-    Case::Display::setupHwcGetActiveConfigCallExpectations(this);
-
-    // We expect a scheduled commit for the display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+TEST_F(InitializeDisplaysTest, initializesDisplays) {
+    // Scheduled by the display transaction, and by powering on each display.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(3);
 
     EXPECT_CALL(static_cast<mock::VSyncTracker&>(
                         mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
-                nextAnticipatedVSyncTimeFrom(_))
+                nextAnticipatedVSyncTimeFrom(_, _))
             .WillRepeatedly(Return(0));
 
-    // --------------------------------------------------------------------
-    // Invocation
-
     FTL_FAKE_GUARD(kMainThreadContext, mFlinger.initializeDisplays());
 
-    // --------------------------------------------------------------------
-    // Postconditions
+    for (const auto& display : {mInnerDisplay, mOuterDisplay}) {
+        const auto token = display->getDisplayToken().promote();
+        ASSERT_TRUE(token);
 
-    // The primary display should have a current state
-    ASSERT_TRUE(hasCurrentDisplayState(primaryDisplay.token()));
-    const auto& primaryDisplayState = getCurrentDisplayState(primaryDisplay.token());
+        ASSERT_TRUE(hasCurrentDisplayState(token));
+        const auto& state = getCurrentDisplayState(token);
 
-    // The primary display state should be reset
-    EXPECT_EQ(ui::DEFAULT_LAYER_STACK, primaryDisplayState.layerStack);
-    EXPECT_EQ(ui::ROTATION_0, primaryDisplayState.orientation);
-    EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.orientedDisplaySpaceRect);
-    EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.layerStackSpaceRect);
+        const ui::LayerStack expectedLayerStack = display == mInnerDisplay
+                ? ui::DEFAULT_LAYER_STACK
+                : ui::LayerStack::fromValue(ui::DEFAULT_LAYER_STACK.id + 1);
 
-    // The width and height should both be zero
-    EXPECT_EQ(0u, primaryDisplayState.width);
-    EXPECT_EQ(0u, primaryDisplayState.height);
+        EXPECT_EQ(expectedLayerStack, state.layerStack);
+        EXPECT_EQ(ui::ROTATION_0, state.orientation);
+        EXPECT_EQ(Rect::INVALID_RECT, state.orientedDisplaySpaceRect);
+        EXPECT_EQ(Rect::INVALID_RECT, state.layerStackSpaceRect);
 
-    // The display should be set to PowerMode::ON
-    ASSERT_TRUE(hasDisplayDevice(primaryDisplay.token()));
-    auto displayDevice = primaryDisplay.mutableDisplayDevice();
-    EXPECT_EQ(PowerMode::ON, displayDevice->getPowerMode());
+        EXPECT_EQ(0u, state.width);
+        EXPECT_EQ(0u, state.height);
 
-    // The display transaction needed flag should be set.
+        ASSERT_TRUE(hasDisplayDevice(token));
+        EXPECT_EQ(PowerMode::ON, getDisplayDevice(token).getPowerMode());
+    }
+
     EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
index 7206e29..20a3315 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -17,31 +17,108 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <gui/SurfaceComposerClient.h>
 #include "DisplayTransactionTestHelpers.h"
 
 namespace android {
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using android::hardware::graphics::composer::V2_1::Error;
 
 class NotifyExpectedPresentTest : public DisplayTransactionTest {
 public:
     void SetUp() override {
-        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
-        FakeHwcDisplayInjector(mDisplay->getPhysicalId(), hal::DisplayType::PHYSICAL, kIsPrimary)
+        const auto display = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
+        mPhysicalDisplayId = display->getPhysicalId();
+        FakeHwcDisplayInjector(mPhysicalDisplayId, hal::DisplayType::PHYSICAL, /*isPrimary=*/true)
                 .setPowerMode(hal::PowerMode::ON)
                 .inject(&mFlinger, mComposer);
+
+        ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
+                                                                      TimePoint::fromNs(0),
+                                                                      kFps60Hz));
+        mCompositor = std::make_unique<Compositor>(mPhysicalDisplayId, mFlinger);
     }
 
 protected:
-    sp<DisplayDevice> mDisplay;
-    static constexpr bool kIsPrimary = true;
-    static constexpr hal::HWDisplayId HWC_DISPLAY_ID =
-            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
-};
+    void setTransactionState() {
+        ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
+        TransactionInfo transaction;
+        mFlinger.setTransactionState(FrameTimelineInfo{}, transaction.states, transaction.displays,
+                                     transaction.flags, transaction.applyToken,
+                                     transaction.inputWindowCommands,
+                                     TimePoint::now().ns() + s2ns(1), transaction.isAutoTimestamp,
+                                     transaction.unCachedBuffers,
+                                     /*HasListenerCallbacks=*/false, transaction.callbacks,
+                                     transaction.id, transaction.mergedTransactionIds);
+    }
 
-TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
-    const auto physicDisplayId = mDisplay->getPhysicalId();
-    auto expectedPresentTime = systemTime() + ms2ns(10);
+    struct TransactionInfo {
+        Vector<ComposerState> states;
+        Vector<DisplayState> displays;
+        uint32_t flags = 0;
+        sp<IBinder> applyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance());
+        InputWindowCommands inputWindowCommands;
+        int64_t desiredPresentTime = 0;
+        bool isAutoTimestamp = false;
+        FrameTimelineInfo frameTimelineInfo{};
+        std::vector<client_cache_t> unCachedBuffers;
+        uint64_t id = static_cast<uint64_t>(-1);
+        std::vector<uint64_t> mergedTransactionIds;
+        std::vector<ListenerCallbacks> callbacks;
+    };
+
+    struct Compositor final : ICompositor {
+        explicit Compositor(PhysicalDisplayId displayId, TestableSurfaceFlinger& surfaceFlinger)
+              : displayId(displayId), surfaceFlinger(surfaceFlinger) {}
+
+        void sendNotifyExpectedPresentHint(PhysicalDisplayId id) override {
+            surfaceFlinger.sendNotifyExpectedPresentHint(id);
+        }
+
+        bool commit(PhysicalDisplayId, const scheduler::FrameTargets&) override {
+            return committed;
+        }
+
+        CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
+                                             const scheduler::FrameTargeters& targeters) override {
+            pacesetterIds.composite = pacesetterId;
+            CompositeResultsPerDisplay results;
+
+            for (const auto& [id, targeter] : targeters) {
+                vsyncIds.composite.emplace_back(id, targeter->target().vsyncId());
+                surfaceFlinger.resetNotifyExpectedPresentHintState(pacesetterId);
+                results.try_emplace(id,
+                                    CompositeResult{.compositionCoverage =
+                                                            CompositionCoverage::Hwc});
+            }
+
+            return results;
+        }
+
+        void sample() override {}
+        void configure() override {}
+
+        struct {
+            PhysicalDisplayId commit;
+            PhysicalDisplayId composite;
+        } pacesetterIds;
+
+        using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
+        struct {
+            VsyncIds commit;
+            VsyncIds composite;
+        } vsyncIds;
+
+        bool committed = true;
+        PhysicalDisplayId displayId;
+        TestableSurfaceFlinger& surfaceFlinger;
+    };
+
+    PhysicalDisplayId mPhysicalDisplayId;
+    std::unique_ptr<Compositor> mCompositor;
+    static constexpr hal::HWDisplayId kHwcDisplayId =
+            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
     static constexpr Fps kFps60Hz = 60_Hz;
     static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
     static constexpr int32_t kFrameInterval60HzNs = kFps60Hz.getPeriodNsecs();
@@ -49,89 +126,199 @@
     static constexpr Period kVsyncPeriod =
             Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
     static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs);
-    static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0);
+};
 
-    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
-                                                                  kLastExpectedPresentTimestamp,
-                                                                  kFps60Hz));
-
-    {
-        // Very first ExpectedPresent after idle, no previous timestamp
-        EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
-                .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
-                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
-                                                 kTimeoutNs);
-    }
-    {
-        // Absent timeoutNs
+TEST_F(NotifyExpectedPresentTest, noNotifyExpectedPresentHintCall_absentTimeout) {
+    auto expectedPresentTime = TimePoint::now().ns() + ms2ns(10);
+    ASSERT_NO_FATAL_FAILURE(
+            mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
+                                                  TimePoint::fromNs(expectedPresentTime),
+                                                  kFps60Hz));
+    EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+    for (int i = 0; i < 5; i++) {
         expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  /*timeoutOpt*/ std::nullopt);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentHint_zeroTimeout) {
+    auto expectedPresentTime = TimePoint::now().ns() + ms2ns(10);
     {
-        // Timeout is 0
-        expectedPresentTime += kFrameInterval60HzNs;
+        // Very first ExpectedPresent after idle, no previous timestamp.
         EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
                 .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
-                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
-                                                 Period::fromNs(0));
-    }
-    {
-        // ExpectedPresent is after the timeoutNs
-        expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
-                .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+
+        // Present frame
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Present happens and NotifyExpectedPresentHintStatus is start.
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+    }
+    {
+        mCompositor->committed = false;
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(static_cast<mock::VSyncTracker&>(
+                            mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
+                    nextAnticipatedVSyncTimeFrom(_, _))
+                .WillRepeatedly(Return(expectedPresentTime));
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Hint sent
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+    {
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(static_cast<mock::VSyncTracker&>(
+                            mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
+                    nextAnticipatedVSyncTimeFrom(_, _))
+                .WillRepeatedly(Return(expectedPresentTime));
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        // Hint is executed
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+}
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
+    auto expectedPresentTime = TimePoint::now().ns() + ms2ns(10);
+    {
+        // Very first ExpectedPresent after idle, no previous timestamp
+        mCompositor->committed = false;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+    {
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsScheduledOnTx(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsScheduledOnTx(mPhysicalDisplayId));
+        {
+            EXPECT_CALL(*mComposer,
+                        notifyExpectedPresent(kHwcDisplayId, expectedPresentTime,
+                                              kFrameInterval60HzNs))
+                    .WillOnce(Return(Error::NONE));
+            // Hint sent with the setTransactionState
+            setTransactionState();
+            ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        }
+    }
+    {
+        // ExpectedPresentTime is after the timeoutNs
+        mCompositor->committed = true;
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsScheduledOnTx(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Present happens notifyExpectedPresentHintStatus is Start
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+
+        // Another expectedPresent after timeout
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
         // ExpectedPresent has not changed
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
-        // ExpectedPresent is after the last reported ExpectedPresent.
+        // ExpectedPresent is after the last reported ExpectedPresent and within timeout.
         expectedPresentTime += kFrameInterval60HzNs;
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
         // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
         // representing we changed our decision and want to present earlier than previously
         // reported.
+        mCompositor->committed = false;
         expectedPresentTime -= kFrameInterval120HzNs;
         EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
                 .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsScheduledOnPresent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
     }
 }
 
 TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) {
-    const auto physicDisplayId = mDisplay->getPhysicalId();
-    const auto now = systemTime();
+    const auto now = TimePoint::now().ns();
     auto expectedPresentTime = now;
     static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs());
 
-    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
                                                                   TimePoint::fromNs(now),
                                                                   Fps::fromValue(0)));
     static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
@@ -147,7 +334,7 @@
 
     struct FrameRateIntervalTestData {
         int32_t frameIntervalNs;
-        bool callExpectedPresent;
+        bool callNotifyExpectedPresentHint;
     };
     const std::vector<FrameRateIntervalTestData> frameIntervals = {
             {kFrameIntervalNs60Hz, true},  {kFrameIntervalNs96Hz, true},
@@ -159,21 +346,39 @@
             {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true},
     };
 
-    for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) {
-        {
-            expectedPresentTime += frameIntervalNs;
-            if (callExpectedPresent) {
-                EXPECT_CALL(*mComposer,
-                            notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                                  frameIntervalNs))
-                        .WillOnce(Return(Error::NONE));
-            } else {
-                EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-            }
-            mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
-                                                     TimePoint::fromNs(expectedPresentTime),
-                                                     Fps::fromPeriodNsecs(frameIntervalNs),
-                                                     kTimeoutNs);
+    for (size_t i = 0; i < frameIntervals.size(); i++) {
+        const auto& [frameIntervalNs, callNotifyExpectedPresentHint] = frameIntervals[i];
+        expectedPresentTime += frameIntervalNs;
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime),
+                                                 Fps::fromPeriodNsecs(frameIntervalNs), kTimeoutNs);
+
+        EXPECT_CALL(static_cast<mock::VSyncTracker&>(
+                            mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
+                    nextAnticipatedVSyncTimeFrom(_, _))
+                .WillRepeatedly(Return(expectedPresentTime));
+        if (callNotifyExpectedPresentHint) {
+            mCompositor->committed = false;
+            ASSERT_TRUE(mFlinger.verifyHintIsScheduledOnPresent(mPhysicalDisplayId))
+                    << "Hint not scheduled for frameInterval " << frameIntervalNs << " at index "
+                    << i;
+            EXPECT_CALL(*mComposer,
+                        notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
+                    .WillOnce(Return(Error::NONE));
+        } else {
+            // Only lastExpectedPresentTime is updated
+            EXPECT_TRUE(
+                    mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime))
+                    << "LastExpectedPresentTime for frameInterval " << frameIntervalNs
+                    << "at index " << i << " did not match for frameInterval " << frameIntervalNs;
+            EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        }
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+
+        if (callNotifyExpectedPresentHint) {
+            // Present resumes the calls to the notifyExpectedPresentHint.
+            mCompositor->committed = true;
+            mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
         }
     }
 }
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index b80cb66..c3934e6 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -17,84 +17,18 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlingerPowerHintTest"
 
-#include <compositionengine/Display.h>
-#include <compositionengine/mock/DisplaySurface.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <renderengine/mock/RenderEngine.h>
-#include <algorithm>
 #include <chrono>
-#include <memory>
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/DisplayHardware/MockPowerAdvisor.h"
-#include "mock/MockTimeStats.h"
-#include "mock/system/window/MockNativeWindow.h"
 
-using namespace android;
-using namespace android::Hwc2::mock;
-using namespace android::hardware::power;
+#include "CommitAndCompositeTest.h"
+
 using namespace std::chrono_literals;
-using namespace testing;
+using testing::_;
+using testing::Return;
 
 namespace android {
 namespace {
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 
-constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
-constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
-constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
-constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
-
-class SurfaceFlingerPowerHintTest : public Test {
-public:
-    void SetUp() override;
-
-protected:
-    TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
-    sp<DisplayDevice> mDisplay;
-    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
-            sp<compositionengine::mock::DisplaySurface>::make();
-    sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
-    mock::TimeStats* mTimeStats = new mock::TimeStats();
-    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
-    Hwc2::mock::Composer* mComposer = nullptr;
-};
-
-void SurfaceFlingerPowerHintTest::SetUp() {
-    mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
-    mComposer = new Hwc2::mock::Composer();
-    mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
-    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
-    mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
-    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
-    mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
-    static constexpr bool kIsPrimary = true;
-    FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
-            .setPowerMode(hal::PowerMode::ON)
-            .inject(&mFlinger, mComposer);
-    auto compostionEngineDisplayArgs =
-            compositionengine::DisplayCreationArgsBuilder()
-                    .setId(DEFAULT_DISPLAY_ID)
-                    .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
-                    .setPowerAdvisor(mPowerAdvisor)
-                    .setName("injected display")
-                    .build();
-    auto compositionDisplay =
-            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
-                                                   std::move(compostionEngineDisplayArgs));
-    mDisplay =
-            FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
-                                      ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary)
-                    .setDisplaySurface(mDisplaySurface)
-                    .setNativeWindow(mNativeWindow)
-                    .setPowerMode(hal::PowerMode::ON)
-                    .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
-                    .skipRegisterDisplay()
-                    .inject();
-}
+class SurfaceFlingerPowerHintTest : public CommitAndCompositeTest {};
 
 TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) {
     ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true));
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index c0796df..1bae5ff 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -255,10 +255,15 @@
             colorModes.push_back(ColorMode::DISPLAY_P3);
         }
 
-        mFlinger.mutablePhysicalDisplays().emplace_or_replace(*displayId, displayToken, *displayId,
-                                                              *connectionType,
-                                                              makeModes(activeMode),
-                                                              std::move(colorModes), std::nullopt);
+        const auto it = mFlinger.mutablePhysicalDisplays()
+                                .emplace_or_replace(*displayId, displayToken, *displayId,
+                                                    *connectionType, makeModes(activeMode),
+                                                    std::move(colorModes), std::nullopt)
+                                .first;
+
+        FTL_FAKE_GUARD(kMainThreadContext,
+                       mFlinger.mutableDisplayModeController()
+                               .registerDisplay(it->second.snapshot(), activeMode->getId(), {}));
     }
 
     state.isSecure = static_cast<bool>(Case::Display::SECURE);
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.cpp b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
new file mode 100644
index 0000000..7b92a5b
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+
+namespace android::scheduler {
+
+TestableScheduler::TestableScheduler(RefreshRateSelectorPtr selectorPtr,
+                                     TestableSurfaceFlinger& testableSurfaceFlinger,
+                                     ISchedulerCallback& callback)
+      : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
+                          std::make_shared<android::mock::VSyncTracker>(), std::move(selectorPtr),
+                          testableSurfaceFlinger.getFactory(),
+                          testableSurfaceFlinger.getTimeStats(), callback) {}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index c0255d3..198a5de 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -32,25 +32,25 @@
 #include "mock/MockVSyncTracker.h"
 #include "mock/MockVsyncController.h"
 
+namespace android {
+class TestableSurfaceFlinger;
+} // namespace android
+
 namespace android::scheduler {
 
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
-    TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback,
-                      IVsyncTrackerCallback& vsyncTrackerCallback)
-          : TestableScheduler(std::make_unique<mock::VsyncController>(),
-                              std::make_shared<mock::VSyncTracker>(), std::move(selectorPtr),
-                              sp<VsyncModulator>::make(VsyncConfigSet{}), callback,
-                              vsyncTrackerCallback) {}
+    TestableScheduler(RefreshRateSelectorPtr selectorPtr,
+                      TestableSurfaceFlinger& testableSurfaceFlinger, ISchedulerCallback& callback);
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& schedulerCallback,
-                      IVsyncTrackerCallback& vsyncTrackerCallback)
+                      surfaceflinger::Factory& factory, TimeStats& timeStats,
+                      ISchedulerCallback& schedulerCallback)
           : Scheduler(*this, schedulerCallback,
                       (FeatureFlags)Feature::kContentDetection |
                               Feature::kSmallDirtyContentDetection,
-                      std::move(modulatorPtr), vsyncTrackerCallback) {
+                      factory, selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
                         std::move(tracker));
@@ -71,9 +71,14 @@
         Scheduler::onFrameSignal(compositor, vsyncId, TimePoint());
     }
 
-    // Used to inject mock event thread.
-    ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
-        return Scheduler::createConnection(std::move(eventThread));
+    void setEventThread(Cycle cycle, std::unique_ptr<EventThread> eventThreadPtr) {
+        if (cycle == Cycle::Render) {
+            mRenderEventThread = std::move(eventThreadPtr);
+            mRenderEventConnection = mRenderEventThread->createEventConnection();
+        } else {
+            mLastCompositeEventThread = std::move(eventThreadPtr);
+            mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
+        }
     }
 
     auto refreshRateSelector() { return pacesetterSelectorPtr(); }
@@ -106,6 +111,11 @@
         Scheduler::unregisterDisplay(displayId);
     }
 
+    void setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        Scheduler::setDisplayPowerMode(displayId, powerMode);
+    }
+
     std::optional<PhysicalDisplayId> pacesetterDisplayId() const NO_THREAD_SAFETY_ANALYSIS {
         return mPacesetterDisplayId;
     }
@@ -124,7 +134,6 @@
 
     using Scheduler::resyncAllToHardwareVsync;
 
-    auto& mutableAppConnectionHandle() { return mAppConnectionHandle; }
     auto& mutableLayerHistory() { return mLayerHistory; }
     auto& mutableAttachedChoreographers() { return mAttachedChoreographers; }
 
@@ -191,10 +200,6 @@
         mPolicy.cachedModeChangedParams.reset();
     }
 
-    void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
-        Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
-    }
-
     void setInitialHwVsyncEnabled(PhysicalDisplayId id, bool enabled) {
         auto schedule = getVsyncSchedule(id);
         std::lock_guard<std::mutex> lock(schedule->mHwVsyncLock);
@@ -219,6 +224,7 @@
         return {};
     }
     void sample() override {}
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 22cb24b..265f804 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -18,10 +18,12 @@
 
 #include <algorithm>
 #include <chrono>
+#include <memory>
 #include <variant>
 
 #include <ftl/fake_guard.h>
 #include <ftl/match.h>
+#include <gui/LayerMetadata.h>
 #include <gui/ScreenCaptureResults.h>
 #include <ui/DynamicDisplayInfo.h>
 
@@ -38,14 +40,15 @@
 #include "FrameTracer/FrameTracer.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/RequestedLayerState.h"
 #include "Layer.h"
 #include "NativeWindowSurface.h"
 #include "RenderArea.h"
 #include "Scheduler/MessageQueue.h"
 #include "Scheduler/RefreshRateSelector.h"
-#include "StartPropertySetThread.h"
 #include "SurfaceFlinger.h"
 #include "TestableScheduler.h"
+#include "android/gui/ISurfaceComposerClient.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/DisplayHardware/MockPowerAdvisor.h"
@@ -53,7 +56,6 @@
 #include "mock/MockFrameTimeline.h"
 #include "mock/MockFrameTracer.h"
 #include "mock/MockSchedulerCallback.h"
-#include "mock/MockVsyncTrackerCallback.h"
 #include "mock/system/window/MockNativeWindow.h"
 
 #include "Scheduler/VSyncTracker.h"
@@ -95,10 +97,6 @@
         return std::make_unique<scheduler::FakePhaseOffsets>();
     }
 
-    sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
-        return sp<StartPropertySetThread>::make(timestampPropertyValue);
-    }
-
     sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) override {
         return sp<DisplayDevice>::make(creationArgs);
     }
@@ -133,7 +131,7 @@
 
     sp<Layer> createEffectLayer(const LayerCreationArgs&) override { return nullptr; }
 
-    sp<LayerFE> createLayerFE(const std::string& layerName) override {
+    sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* /* owner */) override {
         return sp<LayerFE>::make(layerName);
     }
 
@@ -203,9 +201,12 @@
         mFlinger->mCompositionEngine->setTimeStats(timeStats);
     }
 
-    enum class SchedulerCallbackImpl { kNoOp, kMock };
+    void setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine> compositionEngine) {
+        mFlinger->mCompositionEngine = std::move(compositionEngine);
+    }
 
-    enum class VsyncTrackerCallbackImpl { kNoOp, kMock };
+    enum class SchedulerCallbackImpl { kNoOp, kMock };
 
     struct DefaultDisplayMode {
         // The ID of the injected RefreshRateSelector and its default display mode.
@@ -216,14 +217,17 @@
 
     using DisplayModesVariant = std::variant<DefaultDisplayMode, RefreshRateSelectorPtr>;
 
-    void setupScheduler(
-            std::unique_ptr<scheduler::VsyncController> vsyncController,
-            std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
-            std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread,
-            DisplayModesVariant modesVariant,
-            SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
-            VsyncTrackerCallbackImpl vsyncTrackerCallbackImpl = VsyncTrackerCallbackImpl::kNoOp,
-            bool useNiceMock = false) {
+    surfaceflinger::Factory& getFactory() { return mFactory; }
+
+    TimeStats& getTimeStats() { return *mFlinger->mTimeStats; }
+
+    void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
+                        std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
+                        std::unique_ptr<EventThread> appEventThread,
+                        std::unique_ptr<EventThread> sfEventThread,
+                        DisplayModesVariant modesVariant,
+                        SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
+                        bool useNiceMock = false) {
         RefreshRateSelectorPtr selectorPtr = ftl::match(
                 modesVariant,
                 [](DefaultDisplayMode arg) {
@@ -234,13 +238,6 @@
                 },
                 [](RefreshRateSelectorPtr selectorPtr) { return selectorPtr; });
 
-        const auto fps = selectorPtr->getActiveMode().fps;
-        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
-
-        mFlinger->mRefreshRateStats =
-                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
-                                                              hal::PowerMode::OFF);
-
         mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
 
         using ISchedulerCallback = scheduler::ISchedulerCallback;
@@ -248,38 +245,26 @@
                 ? static_cast<ISchedulerCallback&>(mNoOpSchedulerCallback)
                 : static_cast<ISchedulerCallback&>(mSchedulerCallback);
 
-        using VsyncTrackerCallback = scheduler::IVsyncTrackerCallback;
-        VsyncTrackerCallback& vsyncTrackerCallback =
-                vsyncTrackerCallbackImpl == VsyncTrackerCallbackImpl::kNoOp
-                ? static_cast<VsyncTrackerCallback&>(mNoOpVsyncTrackerCallback)
-                : static_cast<VsyncTrackerCallback&>(mVsyncTrackerCallback);
-
-        auto modulatorPtr = sp<scheduler::VsyncModulator>::make(
-                mFlinger->mVsyncConfiguration->getCurrentConfigs());
-
         if (useNiceMock) {
             mScheduler =
                     new testing::NiceMock<scheduler::TestableScheduler>(std::move(vsyncController),
                                                                         std::move(vsyncTracker),
                                                                         std::move(selectorPtr),
-                                                                        std::move(modulatorPtr),
-                                                                        schedulerCallback,
-                                                                        vsyncTrackerCallback);
+                                                                        mFactory,
+                                                                        *mFlinger->mTimeStats,
+                                                                        schedulerCallback);
         } else {
             mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                           std::move(vsyncTracker),
-                                                          std::move(selectorPtr),
-                                                          std::move(modulatorPtr),
-                                                          schedulerCallback, vsyncTrackerCallback);
+                                                          std::move(selectorPtr), mFactory,
+                                                          *mFlinger->mTimeStats, schedulerCallback);
         }
 
-        mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms);
+        mScheduler->initVsync(*mTokenManager, 0ms);
 
-        mScheduler->mutableAppConnectionHandle() =
-                mScheduler->createConnection(std::move(appEventThread));
+        mScheduler->setEventThread(scheduler::Cycle::Render, std::move(appEventThread));
+        mScheduler->setEventThread(scheduler::Cycle::LastComposite, std::move(sfEventThread));
 
-        mFlinger->mAppConnectionHandle = mScheduler->mutableAppConnectionHandle();
-        mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
         resetScheduler(mScheduler);
     }
 
@@ -303,17 +288,16 @@
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
         auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
 
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         EXPECT_CALL(*vsyncTracker, currentPeriod())
                 .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
         EXPECT_CALL(*vsyncTracker, minFramePeriod())
                 .WillRepeatedly(
                         Return(Period::fromNs(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread),
                        std::move(sfEventThread), DefaultDisplayMode{options.displayId},
-                       SchedulerCallbackImpl::kNoOp, VsyncTrackerCallbackImpl::kNoOp,
-                       options.useNiceMock);
+                       SchedulerCallbackImpl::kNoOp, options.useNiceMock);
     }
 
     void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
@@ -391,13 +375,14 @@
         LOG_ALWAYS_FATAL_IF(!displayIdOpt);
         const auto displayId = *displayIdOpt;
 
-        constexpr bool kBackpressureGpuComposition = true;
-        scheduler::FrameTargeter frameTargeter(displayId, kBackpressureGpuComposition);
+        scheduler::FrameTargeter frameTargeter(displayId,
+                                               scheduler::Feature::kBackpressureGpuComposition);
 
         frameTargeter.beginFrame({.frameBeginTime = frameTime,
                                   .vsyncId = vsyncId,
                                   .expectedVsyncTime = expectedVsyncTime,
-                                  .sfWorkDuration = 10ms},
+                                  .sfWorkDuration = 10ms,
+                                  .hwcMinWorkDuration = 10ms},
                                  *mScheduler->getVsyncSchedule());
 
         scheduler::FrameTargets targets;
@@ -435,12 +420,21 @@
         commit(kComposite);
     }
 
-    auto createDisplay(const String8& displayName, bool secure, float requestedRefreshRate = 0.0f) {
-        return mFlinger->createDisplay(displayName, secure, requestedRefreshRate);
+    auto createVirtualDisplay(const std::string& displayName, bool isSecure,
+                              float requestedRefreshRate = 0.0f) {
+        static const std::string kTestId =
+                "virtual:libsurfaceflinger_unittest:TestableSurfaceFlinger";
+        return mFlinger->createVirtualDisplay(displayName, isSecure, kTestId, requestedRefreshRate);
     }
 
-    auto destroyDisplay(const sp<IBinder>& displayToken) {
-        return mFlinger->destroyDisplay(displayToken);
+    auto createVirtualDisplay(const std::string& displayName, bool isSecure,
+                              const std::string& uniqueId, float requestedRefreshRate = 0.0f) {
+        return mFlinger->createVirtualDisplay(displayName, isSecure, uniqueId,
+                                              requestedRefreshRate);
+    }
+
+    auto destroyVirtualDisplay(const sp<IBinder>& displayToken) {
+        return mFlinger->destroyVirtualDisplay(displayToken);
     }
 
     auto getDisplay(const sp<IBinder>& displayToken) {
@@ -463,6 +457,7 @@
     void commitTransactionsLocked(uint32_t transactionFlags) {
         Mutex::Autolock lock(mFlinger->mStateLock);
         ftl::FakeGuard guard(kMainThreadContext);
+        mFlinger->processDisplayChangesLocked();
         mFlinger->commitTransactionsLocked(transactionFlags);
     }
 
@@ -490,7 +485,7 @@
         return mFlinger->setPowerModeInternal(display, mode);
     }
 
-    auto renderScreenImpl(std::shared_ptr<const RenderArea> renderArea,
+    auto renderScreenImpl(std::unique_ptr<const RenderArea> renderArea,
                           SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers,
                           const std::shared_ptr<renderengine::ExternalTexture>& buffer,
                           bool regionSampling) {
@@ -516,9 +511,11 @@
 
     auto& getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; }
     auto& getPendingTransactionQueue() {
+        ftl::FakeGuard guard(kMainThreadContext);
         return mFlinger->mTransactionHandler.mPendingTransactionQueues;
     }
     size_t getPendingTransactionCount() {
+        ftl::FakeGuard guard(kMainThreadContext);
         return mFlinger->mTransactionHandler.mPendingTransactionCount.load();
     }
 
@@ -537,7 +534,9 @@
     }
 
     auto setTransactionStateInternal(TransactionState& transaction) {
-        return mFlinger->mTransactionHandler.queueTransaction(std::move(transaction));
+        return FTL_FAKE_GUARD(kMainThreadContext,
+                              mFlinger->mTransactionHandler.queueTransaction(
+                                      std::move(transaction)));
     }
 
     auto flushTransactionQueues() {
@@ -607,6 +606,13 @@
         return mFlinger->getDisplayStats(displayToken, outInfo);
     }
 
+    // Used to add a layer before updateLayerSnapshots is called.
+    // Must have transactionsFlushed enabled for the new layer to be updated.
+    void addLayer(std::unique_ptr<frontend::RequestedLayerState>& layer) {
+        std::scoped_lock<std::mutex> lock(mFlinger->mCreatedLayersLock);
+        mFlinger->mNewLayers.emplace_back(std::move(layer));
+    }
+
     /* ------------------------------------------------------------------------
      * Read-only access to private data to assert post-conditions.
      */
@@ -622,15 +628,20 @@
     }
 
     void injectLegacyLayer(sp<Layer> layer) {
-        mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer;
+        FTL_FAKE_GUARD(kMainThreadContext,
+                       mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer);
     };
 
-    void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); };
+    void releaseLegacyLayer(uint32_t sequence) {
+        FTL_FAKE_GUARD(kMainThreadContext, mFlinger->mLegacyLayers.erase(sequence));
+    };
 
     auto setLayerHistoryDisplayArea(uint32_t displayArea) {
         return mFlinger->mScheduler->onActiveDisplayAreaChanged(displayArea);
     };
-    auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); };
+    auto updateLayerHistory(nsecs_t now) {
+        return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->updateLayerHistory(now));
+    };
     auto setDaltonizerType(ColorBlindnessType type) {
         mFlinger->mDaltonizer.setType(type);
         return mFlinger->updateColorMatrixLocked();
@@ -656,6 +667,7 @@
 
     auto& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; }
 
+    auto& mutableDisplayModeController() { return mFlinger->mDisplayModeController; }
     auto& mutableCurrentState() { return mFlinger->mCurrentState; }
     auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; }
     auto& mutableDisplays() { return mFlinger->mDisplays; }
@@ -690,10 +702,9 @@
         return mFlinger->initTransactionTraceWriter();
     }
 
-    void enableNewFrontEnd() {
-        mFlinger->mLayerLifecycleManagerEnabled = true;
-        mFlinger->mLegacyFrontEndEnabled = false;
-    }
+    // Needed since mLayerLifecycleManagerEnabled is false by default and must
+    // be enabled for tests to go through the new front end path.
+    void enableLayerLifecycleManager() { mFlinger->mLayerLifecycleManagerEnabled = true; }
 
     void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod,
                                          TimePoint expectedPresentTime, Fps frameInterval,
@@ -702,6 +713,36 @@
                                                   frameInterval, timeoutOpt);
     }
 
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        mFlinger->sendNotifyExpectedPresentHint(displayId);
+    }
+
+    bool verifyHintIsScheduledOnPresent(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::ScheduleOnPresent;
+    }
+
+    bool verifyHintIsSent(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Sent;
+    }
+
+    bool verifyHintStatusIsStart(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Start;
+    }
+
+    bool verifyHintStatusIsScheduledOnTx(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::ScheduleOnTx;
+    }
+
+    bool verifyLastExpectedPresentTime(PhysicalDisplayId displayId, nsecs_t expectedPresentTime) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId)
+                       .lastExpectedPresentTimestamp.ns() == expectedPresentTime;
+    }
+
     void setNotifyExpectedPresentData(PhysicalDisplayId displayId,
                                       TimePoint lastExpectedPresentTimestamp,
                                       Fps lastFrameInterval) {
@@ -710,6 +751,11 @@
         displayData.lastFrameInterval = lastFrameInterval;
     }
 
+    void resetNotifyExpectedPresentHintState(PhysicalDisplayId displayId) {
+        mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus =
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Start;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
@@ -754,7 +800,6 @@
         static constexpr int32_t DEFAULT_CONFIG_GROUP = 7;
         static constexpr int32_t DEFAULT_DPI = 320;
         static constexpr hal::HWConfigId DEFAULT_ACTIVE_CONFIG = 0;
-        static constexpr hal::PowerMode DEFAULT_POWER_MODE = hal::PowerMode::ON;
 
         FakeHwcDisplayInjector(HalDisplayId displayId, hal::DisplayType hwcDisplayType,
                                bool isPrimary)
@@ -797,7 +842,7 @@
             return *this;
         }
 
-        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
+        auto& setPowerMode(hal::PowerMode mode) {
             mPowerMode = mode;
             return *this;
         }
@@ -821,9 +866,7 @@
                                                          mHwcDisplayType);
             display->mutableIsConnected() = true;
 
-            if (mPowerMode) {
-                display->setPowerMode(*mPowerMode);
-            }
+            display->setPowerMode(mPowerMode);
 
             flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display);
 
@@ -889,7 +932,7 @@
         int32_t mDpiY = DEFAULT_DPI;
         int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
         hal::HWConfigId mActiveConfig = DEFAULT_ACTIVE_CONFIG;
-        std::optional<hal::PowerMode> mPowerMode = DEFAULT_POWER_MODE;
+        hal::PowerMode mPowerMode = hal::PowerMode::ON;
         const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>*
                 mCapabilities = nullptr;
     };
@@ -939,14 +982,14 @@
 
         auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
             mDisplayModes = std::move(modes);
-            mCreationArgs.activeModeId = activeModeId;
+            mActiveModeId = activeModeId;
             mCreationArgs.refreshRateSelector = nullptr;
             return *this;
         }
 
         auto& setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) {
             mDisplayModes = selectorPtr->displayModes();
-            mCreationArgs.activeModeId = selectorPtr->getActiveMode().modePtr->getId();
+            mActiveModeId = selectorPtr->getActiveMode().modePtr->getId();
             mCreationArgs.refreshRateSelector = std::move(selectorPtr);
             return *this;
         }
@@ -966,7 +1009,7 @@
             return *this;
         }
 
-        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
+        auto& setPowerMode(hal::PowerMode mode) {
             mCreationArgs.initialPowerMode = mode;
             return *this;
         }
@@ -988,8 +1031,9 @@
             return *this;
         }
 
-        auto& skipRegisterDisplay() {
-            mRegisterDisplay = false;
+        // Used to avoid overwriting mocks injected by TestableSurfaceFlinger::setupMockScheduler.
+        auto& skipSchedulerRegistration() {
+            mSchedulerRegistration = false;
             return *this;
         }
 
@@ -1002,12 +1046,24 @@
                                  std::shared_ptr<android::scheduler::VSyncTracker> tracker)
                 NO_THREAD_SAFETY_ANALYSIS {
             const auto displayId = mCreationArgs.compositionDisplay->getDisplayId();
+            LOG_ALWAYS_FATAL_IF(!displayId);
 
             auto& modes = mDisplayModes;
-            auto& activeModeId = mCreationArgs.activeModeId;
+            auto& activeModeId = mActiveModeId;
+            std::optional<Fps> refreshRateOpt;
 
-            if (displayId && !mCreationArgs.refreshRateSelector) {
-                if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) {
+            DisplayDeviceState state;
+            state.isSecure = mCreationArgs.isSecure;
+
+            if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) {
+                LOG_ALWAYS_FATAL_IF(!mConnectionType);
+                LOG_ALWAYS_FATAL_IF(!mHwcDisplayId);
+
+                if (mCreationArgs.isPrimary) {
+                    mFlinger.mutableActiveDisplayId() = *physicalId;
+                }
+
+                if (!mCreationArgs.refreshRateSelector) {
                     if (modes.empty()) {
                         constexpr DisplayModeId kModeId{0};
                         DisplayModePtr mode =
@@ -1029,48 +1085,38 @@
                     mCreationArgs.refreshRateSelector =
                             std::make_shared<scheduler::RefreshRateSelector>(modes, activeModeId);
                 }
+
+                const auto activeModeOpt = modes.get(activeModeId);
+                LOG_ALWAYS_FATAL_IF(!activeModeOpt);
+                refreshRateOpt = activeModeOpt->get()->getPeakFps();
+
+                state.physical = {.id = *physicalId,
+                                  .hwcDisplayId = *mHwcDisplayId,
+                                  .activeMode = activeModeOpt->get()};
+
+                const auto it = mFlinger.mutablePhysicalDisplays()
+                                        .emplace_or_replace(*physicalId, mDisplayToken, *physicalId,
+                                                            *mConnectionType, std::move(modes),
+                                                            ui::ColorModes(), std::nullopt)
+                                        .first;
+
+                mFlinger.mutableDisplayModeController()
+                        .registerDisplay(*physicalId, it->second.snapshot(),
+                                         mCreationArgs.refreshRateSelector);
+
+                if (mFlinger.scheduler() && mSchedulerRegistration) {
+                    mFlinger.scheduler()->registerDisplay(*physicalId,
+                                                          mCreationArgs.refreshRateSelector,
+                                                          std::move(controller),
+                                                          std::move(tracker));
+                }
             }
 
             sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
             mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
 
-            DisplayDeviceState state;
-            state.isSecure = mCreationArgs.isSecure;
-
-            if (mConnectionType) {
-                LOG_ALWAYS_FATAL_IF(!displayId);
-                const auto physicalIdOpt = PhysicalDisplayId::tryCast(*displayId);
-                LOG_ALWAYS_FATAL_IF(!physicalIdOpt);
-                const auto physicalId = *physicalIdOpt;
-
-                if (mCreationArgs.isPrimary) {
-                    mFlinger.mutableActiveDisplayId() = physicalId;
-                }
-
-                LOG_ALWAYS_FATAL_IF(!mHwcDisplayId);
-
-                const auto activeMode = modes.get(activeModeId);
-                LOG_ALWAYS_FATAL_IF(!activeMode);
-                const auto fps = activeMode->get()->getPeakFps();
-
-                state.physical = {.id = physicalId,
-                                  .hwcDisplayId = *mHwcDisplayId,
-                                  .activeMode = activeMode->get()};
-
-                mFlinger.mutablePhysicalDisplays().emplace_or_replace(physicalId, mDisplayToken,
-                                                                      physicalId, *mConnectionType,
-                                                                      std::move(modes),
-                                                                      ui::ColorModes(),
-                                                                      std::nullopt);
-
-                if (mFlinger.scheduler() && mRegisterDisplay) {
-                    mFlinger.scheduler()->registerDisplay(physicalId,
-                                                          display->holdRefreshRateSelector(),
-                                                          std::move(controller),
-                                                          std::move(tracker));
-                }
-
-                display->setActiveMode(activeModeId, fps, fps);
+            if (refreshRateOpt) {
+                display->setActiveMode(activeModeId, *refreshRateOpt, *refreshRateOpt);
             }
 
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
@@ -1084,7 +1130,8 @@
         sp<BBinder> mDisplayToken = sp<BBinder>::make();
         DisplayDeviceCreationArgs mCreationArgs;
         DisplayModes mDisplayModes;
-        bool mRegisterDisplay = true;
+        DisplayModeId mActiveModeId;
+        bool mSchedulerRegistration = true;
         const std::optional<ui::DisplayConnectionType> mConnectionType;
         const std::optional<hal::HWDisplayId> mHwcDisplayId;
     };
@@ -1106,8 +1153,6 @@
     sp<SurfaceFlinger> mFlinger;
     scheduler::mock::SchedulerCallback mSchedulerCallback;
     scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback;
-    scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
-    scheduler::mock::NoOpVsyncTrackerCallback mNoOpVsyncTrackerCallback;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
     scheduler::TestableScheduler* mScheduler = nullptr;
     Hwc2::mock::PowerAdvisor mPowerAdvisor;
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 1f2a1ed..7fb9247 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -17,6 +17,7 @@
 #undef LOG_TAG
 #define LOG_TAG "TransactionApplicationTest"
 
+#include <common/test/FlagUtils.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/mock/DisplaySurface.h>
 #include <gmock/gmock.h>
@@ -34,8 +35,11 @@
 #include "TestableSurfaceFlinger.h"
 #include "TransactionState.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 namespace android {
 
+using namespace com::android::graphics::surfaceflinger;
 using testing::_;
 using testing::Return;
 
@@ -498,6 +502,44 @@
     setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
 }
 
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_AutoRefreshChanged) {
+    SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, false);
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eAutoRefreshChanged |
+                                                                      layer_state_t::
+                                                                              eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesUnSignaledInTheQueue_AutoRefreshChanged) {
+    SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, true);
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eAutoRefreshChanged |
+                                                                      layer_state_t::
+                                                                              eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
+}
+
 TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferChangeClubed) {
     const sp<IBinder> kApplyToken =
             IInterface::asBinder(TransactionCompletedListener::getIInstance());
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index cbb597a..af02330 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -106,7 +106,7 @@
 
 TEST(TransactionProtoParserTest, parseDisplayInfo) {
     frontend::DisplayInfo d1;
-    d1.info.displayId = 42;
+    d1.info.displayId = ui::LogicalDisplayId{42};
     d1.info.logicalWidth = 43;
     d1.info.logicalHeight = 44;
     d1.info.transform.set(1, 2, 3, 4);
diff --git a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
index 4a83d44..d071ce9 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
@@ -105,4 +105,16 @@
     verifyTraceFile();
 }
 
+// Check we cannot write to file if the trace write is disabled.
+TEST_F(TransactionTraceWriterTest, canDisableTraceWriter) {
+    TransactionTraceWriter::getInstance().disable();
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+    EXPECT_NE(access(mFilename.c_str(), F_OK), 0);
+
+    TransactionTraceWriter::getInstance().enable();
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+    EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
+    verifyTraceFile();
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 6a56353..3b09554 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -48,12 +48,13 @@
     Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); }
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
-    bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
+    bool isVSyncInPhase(nsecs_t, Fps) final { return false; }
     void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final {}
-    void setRenderRate(Fps) final {}
+    void setRenderRate(Fps, bool) final {}
     void onFrameBegin(TimePoint, TimePoint) final {}
     void onFrameMissed(TimePoint) final {}
     void dump(std::string&) const final {}
+    bool isCurrentMode(const ftl::NonNull<DisplayModePtr>&) const final { return false; };
 
 protected:
     std::mutex mutable mMutex;
@@ -64,7 +65,7 @@
 public:
     FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {}
 
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final {
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t>) final {
         auto const floor = timePoint % mPeriod;
         if (floor == 0) {
             return timePoint;
@@ -77,7 +78,7 @@
 public:
     VRRStubTracker(nsecs_t period) : StubTracker(period) {}
 
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point) const final {
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) final {
         std::lock_guard lock(mMutex);
         auto const normalized_to_base = time_point - mBase;
         auto const floor = (normalized_to_base) % mPeriod;
@@ -117,7 +118,7 @@
         mCallback.schedule(
                 {.workDuration = mWorkload,
                  .readyDuration = mReadyDuration,
-                 .earliestVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration});
+                 .lastVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration});
 
         for (auto i = 0u; i < iterations - 1; i++) {
             std::unique_lock lock(mMutex);
@@ -130,7 +131,7 @@
 
             mCallback.schedule({.workDuration = mWorkload,
                                 .readyDuration = mReadyDuration,
-                                .earliestVsync = last + mWorkload + mReadyDuration});
+                                .lastVsync = last + mWorkload + mReadyDuration});
         }
 
         // wait for the last callback.
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 2047018..9b70d92 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -46,14 +46,14 @@
 class MockVSyncTracker : public mock::VSyncTracker {
 public:
     MockVSyncTracker(nsecs_t period) : mPeriod{period} {
-        ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_))
+        ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_, _))
                 .WillByDefault(Invoke(this, &MockVSyncTracker::nextVSyncTime));
         ON_CALL(*this, addVsyncTimestamp(_)).WillByDefault(Return(true));
         ON_CALL(*this, currentPeriod())
                 .WillByDefault(Invoke(this, &MockVSyncTracker::getCurrentPeriod));
     }
 
-    nsecs_t nextVSyncTime(nsecs_t timePoint) const {
+    nsecs_t nextVSyncTime(nsecs_t timePoint, std::optional<nsecs_t>) const {
         if (timePoint % mPeriod == 0) {
             return timePoint;
         }
@@ -243,12 +243,12 @@
                                                           mDispatchGroupThreshold,
                                                           mVsyncMoveThreshold);
         CountingCallback cb(mDispatch);
-        const auto result = mDispatch->schedule(cb,
-                                                {.workDuration = 100,
-                                                 .readyDuration = 0,
-                                                 .earliestVsync = 1000});
+        const auto result =
+                mDispatch->schedule(cb,
+                                    {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
         EXPECT_TRUE(result.has_value());
-        EXPECT_EQ(900, *result);
+        EXPECT_EQ(900, result->callbackTime.ns());
+        EXPECT_EQ(1000, result->vsyncTime.ns());
     }
 }
 
@@ -257,12 +257,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = intended});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(900, *result);
+    EXPECT_EQ(900, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     advanceToNextCallback();
 
@@ -277,18 +277,18 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
-    auto result = mDispatch->schedule(cb,
-                                      {.workDuration = 100,
-                                       .readyDuration = 0,
-                                       .earliestVsync = intended});
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(900, *result);
+    EXPECT_EQ(900, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     result =
-            mDispatch->update(cb,
-                              {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+            mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(700, *result);
+    EXPECT_EQ(700, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     advanceToNextCallback();
 
@@ -303,17 +303,18 @@
 
     CountingCallback cb(mDispatch);
     const auto result =
-            mDispatch->update(cb,
-                              {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+            mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended});
     EXPECT_FALSE(result.has_value());
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150));
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(mPeriod)))
+            .WillOnce(Return(1150));
     EXPECT_CALL(mMockClock, alarmAt(_, 1050));
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -324,7 +325,8 @@
     auto const now = 234;
     mMockClock.advanceBy(234);
     auto const workDuration = 10 * mPeriod;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(now + workDuration, std::optional<nsecs_t>(mPeriod)))
             .WillOnce(Return(mPeriod * 11));
     EXPECT_CALL(mMockClock, alarmAt(_, mPeriod));
 
@@ -332,9 +334,10 @@
     const auto result = mDispatch->schedule(cb,
                                             {.workDuration = workDuration,
                                              .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+                                             .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(mPeriod, *result);
+    EXPECT_EQ(mPeriod, result->callbackTime.ns());
+    EXPECT_EQ(workDuration + mPeriod, result->vsyncTime.ns());
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancel) {
@@ -342,12 +345,12 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(mPeriod - 100, *result);
+    EXPECT_EQ(mPeriod - 100, result->callbackTime.ns());
+    EXPECT_EQ(mPeriod, result->vsyncTime.ns());
     EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled);
 }
 
@@ -356,12 +359,12 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(mPeriod - 100, *result);
+    EXPECT_EQ(mPeriod - 100, result->callbackTime.ns());
+    EXPECT_EQ(mPeriod, result->vsyncTime.ns());
     mMockClock.advanceBy(950);
     EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate);
 }
@@ -371,12 +374,12 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     PausingCallback cb(mDispatch, std::chrono::duration_cast<std::chrono::milliseconds>(1s));
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(mPeriod - 100, *result);
+    EXPECT_EQ(mPeriod - 100, result->callbackTime.ns());
+    EXPECT_EQ(mPeriod, result->vsyncTime.ns());
 
     std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); });
     EXPECT_TRUE(cb.waitForPause());
@@ -393,12 +396,12 @@
 
     PausingCallback cb(mDispatch, 50ms);
     cb.stashResource(resource);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(mPeriod - 100, *result);
+    EXPECT_EQ(mPeriod - 100, result->callbackTime.ns());
+    EXPECT_EQ(mPeriod, result->vsyncTime.ns());
 
     std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); });
     EXPECT_TRUE(cb.waitForPause());
@@ -413,7 +416,8 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .Times(4)
             .WillOnce(Return(1055))
             .WillOnce(Return(1063))
@@ -428,8 +432,8 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
-    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
+    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod});
 
     advanceToNextCallback();
     advanceToNextCallback();
@@ -441,7 +445,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(4)
             .WillOnce(Return(1000))
             .WillOnce(Return(2000))
@@ -455,21 +459,21 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 0});
 
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
     EXPECT_THAT(cb.mCalls[0], Eq(1000));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(2));
     EXPECT_THAT(cb.mCalls[1], Eq(2000));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
 
     advanceToNextCallback();
 
@@ -478,7 +482,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(4)
             .WillOnce(Return(10000))
             .WillOnce(Return(1000))
@@ -493,9 +497,8 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0,
-                        {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10});
-    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod * 10});
+    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod});
     mDispatch->cancel(cb1);
 }
 
@@ -507,9 +510,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -522,9 +525,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -542,10 +545,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1,
-                        {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = closeOffset, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
     ASSERT_THAT(cb0.mCalls.size(), Eq(1));
@@ -553,11 +555,9 @@
     ASSERT_THAT(cb1.mCalls.size(), Eq(1));
     EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod));
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 2000});
     mDispatch->schedule(cb1,
-                        {.workDuration = notCloseOffset,
-                         .readyDuration = 0,
-                         .earliestVsync = 2000});
+                        {.workDuration = notCloseOffset, .readyDuration = 0, .lastVsync = 2000});
     advanceToNextCallback();
     ASSERT_THAT(cb1.mCalls.size(), Eq(2));
     EXPECT_THAT(cb1.mCalls[1], Eq(2000));
@@ -577,32 +577,32 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
     EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(3)
             .WillOnce(Return(950))
             .WillOnce(Return(1975))
             .WillOnce(Return(2950));
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 920});
 
     mMockClock.advanceBy(850);
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1900});
     mMockClock.advanceBy(900);
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
     mMockClock.advanceBy(125);
     EXPECT_THAT(cb.mCalls.size(), Eq(2));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2900});
     mMockClock.advanceBy(975);
     EXPECT_THAT(cb.mCalls.size(), Eq(3));
 }
@@ -616,13 +616,11 @@
     tmp = mDispatch->registerCallback(
             [&](auto, auto, auto) {
                 mDispatch->schedule(tmp,
-                                    {.workDuration = 100,
-                                     .readyDuration = 0,
-                                     .earliestVsync = 2000});
+                                    {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
             },
             "o.o");
 
-    mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -631,30 +629,32 @@
     std::optional<nsecs_t> lastTarget;
     tmp = mDispatch->registerCallback(
             [&](auto timestamp, auto, auto) {
-                auto result =
-                        mDispatch->schedule(tmp,
-                                            {.workDuration = 400,
-                                             .readyDuration = 0,
-                                             .earliestVsync = timestamp - mVsyncMoveThreshold});
+                auto result = mDispatch->schedule(tmp,
+                                                  {.workDuration = 400,
+                                                   .readyDuration = 0,
+                                                   .lastVsync = timestamp - mVsyncMoveThreshold});
                 EXPECT_TRUE(result.has_value());
-                EXPECT_EQ(mPeriod + timestamp - 400, *result);
+                EXPECT_EQ(mPeriod + timestamp - 400, result->callbackTime.ns());
+                EXPECT_EQ(mPeriod + timestamp, result->vsyncTime.ns());
                 result = mDispatch->schedule(tmp,
                                              {.workDuration = 400,
                                               .readyDuration = 0,
-                                              .earliestVsync = timestamp});
+                                              .lastVsync = timestamp});
                 EXPECT_TRUE(result.has_value());
-                EXPECT_EQ(mPeriod + timestamp - 400, *result);
+                EXPECT_EQ(mPeriod + timestamp - 400, result->callbackTime.ns());
+                EXPECT_EQ(mPeriod + timestamp, result->vsyncTime.ns());
                 result = mDispatch->schedule(tmp,
                                              {.workDuration = 400,
                                               .readyDuration = 0,
-                                              .earliestVsync = timestamp + mVsyncMoveThreshold});
+                                              .lastVsync = timestamp + mVsyncMoveThreshold});
                 EXPECT_TRUE(result.has_value());
-                EXPECT_EQ(mPeriod + timestamp - 400, *result);
+                EXPECT_EQ(mPeriod + timestamp - 400, result->callbackTime.ns());
+                EXPECT_EQ(mPeriod + timestamp, result->vsyncTime.ns());
                 lastTarget = timestamp;
             },
             "oo");
 
-    mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
     EXPECT_THAT(lastTarget, Eq(1000));
 
@@ -670,16 +670,16 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .lastVsync = 1000});
 
     mMockClock.advanceBy(750);
-    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
-    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 2000});
 
     mMockClock.advanceBy(800);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, lateModifications) {
@@ -692,12 +692,12 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
-    mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000});
-    mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .lastVsync = 2000});
+    mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
     advanceToNextCallback();
@@ -709,8 +709,8 @@
 
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 20000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) {
@@ -720,17 +720,15 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
 
     CountingCallback cb0(mDispatch);
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     mDispatch->cancel(cb0);
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) {
     VSyncDispatch::CallbackToken token(100);
     EXPECT_FALSE(
-            mDispatch
-                    ->schedule(token,
-                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000})
+            mDispatch->schedule(token, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000})
                     .has_value());
     EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error));
 }
@@ -738,33 +736,33 @@
 TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) {
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(500, *result);
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_EQ(500, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
+    result = mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(900, *result);
+    EXPECT_EQ(900, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 }
 
 // b/1450138150
 TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipAScheduledTargetVSync) {
-    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
 
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(500, *result);
+    EXPECT_EQ(500, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
     mMockClock.advanceBy(400);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(1200, *result);
+    EXPECT_EQ(1200, result->callbackTime.ns());
+    EXPECT_EQ(2000, result->vsyncTime.ns());
 
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -772,58 +770,60 @@
 
 // b/1450138150
 TEST_F(VSyncDispatchTimerQueueTest, movesCallbackBackwardsAndSkipAScheduledTargetVSync) {
-    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
 
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
     EXPECT_CALL(mMockClock, alarmAt(_, 400)).InSequence(seq);
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(500, *result);
+    EXPECT_EQ(500, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
     mMockClock.advanceBy(400);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(400, *result);
+    EXPECT_EQ(400, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .Times(2)
             .WillOnce(Return(1000))
             .WillOnce(Return(1002));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(500, *result);
+    EXPECT_EQ(500, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
     mMockClock.advanceBy(400);
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(602, *result);
+    EXPECT_EQ(602, result->callbackTime.ns());
+    EXPECT_EQ(1002, result->vsyncTime.ns());
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) {
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(500, *result);
+    EXPECT_EQ(500, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
     advanceToNextCallback();
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000});
+    result =
+            mDispatch->schedule(cb0, {.workDuration = 1100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(900, *result);
+    EXPECT_EQ(900, result->callbackTime.ns());
+    EXPECT_EQ(2000, result->vsyncTime.ns());
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, canScheduleLargeNegativeOffset) {
@@ -832,39 +832,40 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq);
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(500, *result);
+    EXPECT_EQ(500, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
     advanceToNextCallback();
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000});
+    result =
+            mDispatch->schedule(cb0, {.workDuration = 1900, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(1100, *result);
+    EXPECT_EQ(1100, result->callbackTime.ns());
+    EXPECT_EQ(3000, result->vsyncTime.ns());
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) {
-    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
 
     EXPECT_CALL(mMockClock, alarmAt(_, 600));
 
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(600, *result);
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(600, *result);
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(2000, result->vsyncTime.ns());
 
     advanceToNextCallback();
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesAffectSchedulingState) {
-    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
 
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
@@ -872,15 +873,15 @@
 
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(600, *result);
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(0, *result);
+    EXPECT_EQ(0, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     advanceToNextCallback();
 }
@@ -892,10 +893,10 @@
     VSyncCallbackRegistration cb(
             mDispatch, [](auto, auto, auto) {}, "");
     VSyncCallbackRegistration cb1(std::move(cb));
-    cb.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     cb.cancel();
 
-    cb1.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     cb1.cancel();
 }
 
@@ -908,10 +909,10 @@
     VSyncCallbackRegistration cb1(
             mDispatch, [](auto, auto, auto) {}, "");
     cb1 = std::move(cb);
-    cb.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     cb.cancel();
 
-    cb1.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     cb1.cancel();
 }
 
@@ -924,18 +925,18 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(600, *result);
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(1900, *result);
+    EXPECT_EQ(1900, result->callbackTime.ns());
+    EXPECT_EQ(2000, result->vsyncTime.ns());
     mMockClock.advanceBy(80);
 
     EXPECT_THAT(cb1.mCalls.size(), Eq(1));
@@ -946,30 +947,65 @@
 // If the same callback tries to reschedule itself after it's too late, timer opts to apply the
 // update later, as opposed to blocking the calling thread.
 TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminentSameCallback) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
+
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
     EXPECT_CALL(mMockClock, alarmAt(_, 1630)).InSequence(seq);
     CountingCallback cb(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(600, *result);
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb, {.workDuration = 370, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(1630, *result);
+    EXPECT_EQ(1630, result->callbackTime.ns());
+    EXPECT_EQ(2000, result->vsyncTime.ns());
     mMockClock.advanceBy(80);
 
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
 }
 
 // b/154303580.
+// If the same callback tries to reschedule itself after it's too late, timer opts to apply the
+// update later, as opposed to blocking the calling thread.
+TEST_F(VSyncDispatchTimerQueueTest, doesntSkipSchedulingIfTimerReschedulingIsImminentSameCallback) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
+
+    Sequence seq;
+    EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
+    EXPECT_CALL(mMockClock, alarmAt(_, 1630)).InSequence(seq);
+    CountingCallback cb(mDispatch);
+
+    auto result =
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
+
+    mMockClock.setLag(100);
+    mMockClock.advanceBy(620);
+
+    result = mDispatch->schedule(cb, {.workDuration = 370, .readyDuration = 0, .lastVsync = 2000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
+    mMockClock.advanceBy(80);
+
+    ASSERT_EQ(1, cb.mCalls.size());
+    EXPECT_EQ(1000, cb.mCalls[0]);
+
+    ASSERT_EQ(1, cb.mWakeupTime.size());
+    EXPECT_EQ(600, cb.mWakeupTime[0]);
+}
+
+// b/154303580.
 TEST_F(VSyncDispatchTimerQueueTest, skipsRearmingWhenNotNextScheduled) {
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
@@ -978,14 +1014,14 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(1900, *result);
+    EXPECT_EQ(1900, result->callbackTime.ns());
+    EXPECT_EQ(2000, result->vsyncTime.ns());
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
@@ -1007,14 +1043,14 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(1900, *result);
+    EXPECT_EQ(1900, result->callbackTime.ns());
+    EXPECT_EQ(2000, result->vsyncTime.ns());
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
@@ -1034,23 +1070,25 @@
     CountingCallback cb2(mDispatch);
 
     Sequence seq;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(1000));
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(1000));
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_EQ(600, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
+    result = mDispatch->schedule(cb2, {.workDuration = 390, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(610, *result);
+    EXPECT_EQ(610, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(700);
@@ -1070,12 +1108,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 70,
-                                             .readyDuration = 30,
-                                             .earliestVsync = intended});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 70, .readyDuration = 30, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(900, *result);
+    EXPECT_EQ(900, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -1087,15 +1125,15 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) {
-    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
 
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
@@ -1110,7 +1148,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, doesNotUpdatesVsyncTimeForCloseWakeupTime) {
-    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
 
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
@@ -1118,8 +1156,8 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
@@ -1134,44 +1172,44 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, skipAVsyc) {
-    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
 
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(500, *result);
+    EXPECT_EQ(500, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
     mMockClock.advanceBy(300);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(1200, *result);
+    EXPECT_EQ(1200, result->callbackTime.ns());
+    EXPECT_EQ(2000, result->vsyncTime.ns());
 
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, dontskipAVsyc) {
-    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
 
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
     EXPECT_CALL(mMockClock, alarmAt(_, 300)).InSequence(seq);
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(500, *result);
+    EXPECT_EQ(500, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
     mMockClock.advanceBy(300);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(300, *result);
+    EXPECT_EQ(300, result->callbackTime.ns());
+    EXPECT_EQ(1000, result->vsyncTime.ns());
 
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -1204,9 +1242,11 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    const auto scheduleResult =
+            entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
+                           *mStubTracker, 0);
+    EXPECT_EQ(900, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(900));
@@ -1219,16 +1259,19 @@
     auto const duration = 500;
     auto const now = 8750;
 
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(now + duration, std::optional<nsecs_t>(994)))
             .Times(1)
             .WillOnce(Return(10000));
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994},
-                               *mStubTracker.get(), now)
-                        .has_value());
+    const auto scheduleResult =
+            entry.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 994},
+                           *mStubTracker, now);
+    EXPECT_EQ(9500, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(10000, scheduleResult.vsyncTime.ns());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(9500));
@@ -1249,9 +1292,11 @@
             },
             mVsyncMoveThreshold);
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    const auto scheduleResult =
+            entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
+                           *mStubTracker, 0);
+    EXPECT_EQ(900, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(900));
@@ -1272,7 +1317,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(2)
             .WillOnce(Return(1000))
             .WillOnce(Return(1020));
@@ -1281,17 +1326,19 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    entry.update(*mStubTracker.get(), 0);
+    entry.update(*mStubTracker, 0);
     EXPECT_FALSE(entry.wakeupTime());
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    const auto scheduleResult =
+            entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
+                           *mStubTracker, 0);
+    EXPECT_EQ(900, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
     auto wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(wakeup, Eq(900));
 
-    entry.update(*mStubTracker.get(), 0);
+    entry.update(*mStubTracker, 0);
     wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(920));
@@ -1300,9 +1347,10 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    EXPECT_EQ(900,
+              entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
+                             *mStubTracker.get(), 0)
+                      .callbackTime.ns());
     entry.update(*mStubTracker.get(), 0);
 
     auto const wakeup = entry.wakeupTime();
@@ -1313,26 +1361,32 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    auto scheduleResult =
+            entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
+                           *mStubTracker, 0);
+
+    EXPECT_EQ(900, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
     entry.executing(); // 1000 is executing
     // had 1000 not been executing, this could have been scheduled for time 800.
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    scheduleResult = entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
+                                    *mStubTracker, 0);
+    EXPECT_EQ(1800, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(2000, scheduleResult.vsyncTime.ns());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    scheduleResult = entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500},
+                                    *mStubTracker, 0);
+    EXPECT_EQ(1950, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(2000, scheduleResult.vsyncTime.ns());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1950));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    scheduleResult = entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 1001},
+                                    *mStubTracker, 0);
+    EXPECT_EQ(1800, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(2000, scheduleResult.vsyncTime.ns());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 }
@@ -1343,54 +1397,75 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     Sequence seq;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500)))
             .InSequence(seq)
             .WillOnce(Return(1000));
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500)))
             .InSequence(seq)
             .WillOnce(Return(1000));
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold,
+                                             std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    auto scheduleResult =
+            entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
+                           *mStubTracker, 0);
+    EXPECT_EQ(900, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
 
     entry.executing(); // 1000 is executing
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    scheduleResult = entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
+                                    *mStubTracker, 0);
+    EXPECT_EQ(1800, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(2000, scheduleResult.vsyncTime.ns());
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) {
-    VSyncDispatchTimerQueueEntry entry(
-            "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    VSyncDispatchTimerQueueEntry entry("test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
+    EXPECT_EQ(900,
+              entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
+                             *mStubTracker, 0)
+                      .callbackTime.ns());
+    EXPECT_EQ(800,
+              entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
+                             *mStubTracker, 0)
+                      .callbackTime.ns());
+    EXPECT_EQ(950,
+              entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500},
+                             *mStubTracker, 0)
+                      .callbackTime.ns());
+    {
+        SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
+        EXPECT_EQ(0,
+                  entry.schedule({.workDuration = 1200, .readyDuration = 0, .lastVsync = 500},
+                                 *mStubTracker, 0)
+                          .callbackTime.ns());
+    }
+    {
+        SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
+        EXPECT_EQ(800,
+                  entry.schedule({.workDuration = 1200, .readyDuration = 0, .lastVsync = 500},
+                                 *mStubTracker, 0)
+                          .callbackTime.ns());
+    }
 }
 
-TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdate) {
+TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdateAndDontSkip) {
     static constexpr auto effectualOffset = 200;
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
     EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
-    entry.addPendingWorkloadUpdate({.workDuration = 100, .readyDuration = 0, .earliestVsync = 400});
-    entry.addPendingWorkloadUpdate(
-            {.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400});
+    entry.addPendingWorkloadUpdate(*mStubTracker.get(), 0,
+                                   {.workDuration = 100, .readyDuration = 0, .lastVsync = 400});
+    entry.addPendingWorkloadUpdate(*mStubTracker.get(), 0,
+                                   {.workDuration = effectualOffset,
+                                    .readyDuration = 0,
+                                    .lastVsync = 400});
     EXPECT_TRUE(entry.hasPendingWorkloadUpdate());
-    entry.update(*mStubTracker.get(), 0);
+    entry.update(*mStubTracker, 0);
     EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
     EXPECT_THAT(*entry.wakeupTime(), Eq(mPeriod - effectualOffset));
 }
@@ -1410,9 +1485,11 @@
             },
             mVsyncMoveThreshold);
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500},
-                               *mStubTracker.get(), 0)
-                        .has_value());
+    const auto scheduleResult =
+            entry.schedule({.workDuration = 70, .readyDuration = 30, .lastVsync = 500},
+                           *mStubTracker, 0);
+    EXPECT_EQ(900, scheduleResult.callbackTime.ns());
+    EXPECT_EQ(mPeriod, scheduleResult.vsyncTime.ns());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(900));
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 7a498c9..eafba0a 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -26,12 +26,12 @@
 #include <common/test/FlagUtils.h>
 #include "Scheduler/VSyncPredictor.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
-#include "mock/MockVsyncTrackerCallback.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <algorithm>
 #include <chrono>
+#include <optional>
 #include <utility>
 
 #include <com_android_graphics_surfaceflinger_flags.h>
@@ -75,20 +75,43 @@
     return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution,
                                               DEFAULT_DISPLAY_ID));
 }
+
+class TestClock : public Clock {
+public:
+    TestClock() = default;
+
+    nsecs_t now() const override { return mNow; }
+    void setNow(nsecs_t now) { mNow = now; }
+
+private:
+    nsecs_t mNow = 0;
+};
+
+class ClockWrapper : public Clock {
+public:
+    ClockWrapper(std::shared_ptr<Clock> const& clock) : mClock(clock) {}
+
+    nsecs_t now() const { return mClock->now(); }
+
+private:
+    std::shared_ptr<Clock> const mClock;
+};
+
 } // namespace
 
 struct VSyncPredictorTest : testing::Test {
     nsecs_t mNow = 0;
     nsecs_t mPeriod = 1000;
     ftl::NonNull<DisplayModePtr> mMode = displayMode(mPeriod);
-    scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
     static constexpr size_t kHistorySize = 10;
     static constexpr size_t kMinimumSamplesForPrediction = 6;
     static constexpr size_t kOutlierTolerancePercent = 25;
     static constexpr nsecs_t mMaxRoundingError = 100;
 
-    VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction,
-                           kOutlierTolerancePercent, mVsyncTrackerCallback};
+    std::shared_ptr<TestClock> mClock{std::make_shared<TestClock>()};
+
+    VSyncPredictor tracker{std::make_unique<ClockWrapper>(mClock), mMode, kHistorySize,
+                           kMinimumSamplesForPrediction, kOutlierTolerancePercent};
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -409,8 +432,8 @@
 // See b/151146131
 TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
     const auto mode = displayMode(mPeriod);
-    VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent,
-                           mVsyncTrackerCallback};
+    VSyncPredictor tracker{std::make_unique<ClockWrapper>(mClock), mode, 20,
+                           kMinimumSamplesForPrediction, kOutlierTolerancePercent};
     std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
                                                840923581635, 840940161584, 840956868096,
                                                840973702473, 840990256277, 841007116851,
@@ -597,44 +620,15 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod));
+    tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod), /*applyImmediately*/ false);
 
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 4 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 4 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 7 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod));
-}
-
-TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) {
-    auto last = mNow;
-    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
-        mNow += mPeriod;
-        last = mNow;
-        tracker.addVsyncTimestamp(mNow);
-    }
-
-    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
-
-    tracker.setRenderRate(refreshRate / 4);
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod));
-
-    tracker.setRenderRate(refreshRate / 2);
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod));
-
-    tracker.setRenderRate(refreshRate / 6);
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 6 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 6 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
 TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) {
@@ -646,7 +640,7 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod));
+    tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod), /*applyImmediately*/ false);
 
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
@@ -657,46 +651,179 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
-TEST_F(VSyncPredictorTest, vsyncTrackerCallback) {
+TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
 
-    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
-    NotifyExpectedPresentConfig notifyExpectedPresentConfig;
-    notifyExpectedPresentConfig.notifyExpectedPresentTimeoutNs = Period::fromNs(30).ns();
-
-    hal::VrrConfig vrrConfig;
-    vrrConfig.notifyExpectedPresentConfig = notifyExpectedPresentConfig;
-    vrrConfig.minFrameIntervalNs = refreshRate.getPeriodNsecs();
-
     const int32_t kGroup = 0;
     const auto kResolution = ui::Size(1920, 1080);
-    const auto mode =
-            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfig, kGroup,
-                                                  kResolution, DEFAULT_DISPLAY_ID));
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
 
-    tracker.setDisplayModePtr(mode);
-    auto last = mNow;
-    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
-        EXPECT_CALL(mVsyncTrackerCallback,
-                    onVsyncGenerated(TimePoint::fromNs(last + mPeriod), mode,
-                                     FpsMatcher(refreshRate)))
-                .Times(1);
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
-        mNow += mPeriod;
-        last = mNow;
-        tracker.addVsyncTimestamp(mNow);
-    }
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
 
-    tracker.setRenderRate(refreshRate / 2);
-    {
-        // out of render rate phase
-        EXPECT_CALL(mVsyncTrackerCallback,
-                    onVsyncGenerated(TimePoint::fromNs(mNow + 3 * mPeriod), mode,
-                                     FpsMatcher(refreshRate / 2)))
-                .Times(1);
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod),
-                    Eq(mNow + 3 * mPeriod));
-    }
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    // commit to a vsync in the future
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ false);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+
+    EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(3500), /*applyImmediately*/ false);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+    EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+    EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+    EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2500), /*applyImmediately*/ false);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+    EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+    EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+    EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+    EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(9500, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+    EXPECT_EQ(11500, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+    EXPECT_EQ(13500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+    EXPECT_EQ(16500, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+    EXPECT_EQ(20500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+
+    // matches the previous cadence
+    EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(20500, 20500));
+}
+
+TEST_F(VSyncPredictorTest, minFramePeriodDoesntApplyWhenSameWithRefreshRate) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(1000);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    // Assume that the last vsync is wrong due to a vsync drift. It shouldn't matter.
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1700));
+}
+
+TEST_F(VSyncPredictorTest, setRenderRateExplicitAppliedImmediately) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    // commit to a vsync in the future
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 2000));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000));
+    EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(9000, vrrTracker.nextAnticipatedVSyncTimeFrom(7000, 7000));
+}
+
+TEST_F(VSyncPredictorTest, selectsClosestVsyncAfterInactivity) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(5000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4700));
+    EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+
+    mClock->setNow(50000);
+    EXPECT_EQ(50500, vrrTracker.nextAnticipatedVSyncTimeFrom(50000, 10000));
+}
+
+TEST_F(VSyncPredictorTest, returnsCorrectVsyncWhenLastIsNot) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234));
 }
 
 TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
@@ -714,21 +841,131 @@
                                      .setVrrConfig(std::move(vrrConfig))
                                      .build());
 
-    VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction,
-                              kOutlierTolerancePercent, mVsyncTrackerCallback};
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
 
-    vrrTracker.setRenderRate(minFrameRate);
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
     vrrTracker.addVsyncTimestamp(0);
     EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
-    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1300));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000));
 
     vrrTracker.onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
-    EXPECT_EQ(1500, vrrTracker.nextAnticipatedVSyncTimeFrom(1300));
-    EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(2300));
+    EXPECT_EQ(3500, vrrTracker.nextAnticipatedVSyncTimeFrom(2000, 2000));
+    EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(3500, 3500));
 
-    vrrTracker.onFrameMissed(TimePoint::fromNs(2500));
-    EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2300));
-    EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3300));
+    // Miss when starting 4500 and expect the next vsync will be at 5000 (next one)
+    vrrTracker.onFrameBegin(TimePoint::fromNs(3500), TimePoint::fromNs(2500));
+    vrrTracker.onFrameMissed(TimePoint::fromNs(4500));
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+
+    vrrTracker.onFrameBegin(TimePoint::fromNs(7000), TimePoint::fromNs(6500));
+    EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000));
+}
+
+TEST_F(VSyncPredictorTest, adjustsVrrTimelineTwoClients) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto refreshRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), refreshRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+
+    // App runs ahead
+    EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700));
+    EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3000, 3000));
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+
+    // SF starts to catch up
+    EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(3000), TimePoint::fromNs(0));
+
+    // SF misses last frame (3000) and observes that when committing (4000)
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3700));
+    vrrTracker.onFrameMissed(TimePoint::fromNs(4000));
+
+    // SF wakes up again instead of the (4000) missed frame
+    EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(4500), TimePoint::fromNs(4500));
+
+    // Timeline shifted. The app needs to get the next frame at (7500) as its last frame (6500) will
+    // be presented at (7500)
+    EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(5500), TimePoint::fromNs(4500));
+
+    EXPECT_EQ(8500, vrrTracker.nextAnticipatedVSyncTimeFrom(7500, 7500));
+    EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5500, 5500));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(6500), TimePoint::fromNs(5500));
+}
+
+TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) {
+    tracker.addVsyncTimestamp(1000);
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
+
+    tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ false);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000));
+
+    tracker.setRenderRate(Fps::fromPeriodNsecs(3000), /*applyImmediately*/ false);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000));
+
+    // Check the purge logic works
+    mClock->setNow(20000);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(2000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(8000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000));
+}
+
+// b/329310308
+TEST_F(VSyncPredictorTest, renderRateChangeAfterAppliedImmediately) {
+    tracker.addVsyncTimestamp(1000);
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(2000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(2001), Eq(3000));
+
+    tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000));
+
+    tracker.setRenderRate(Fps::fromPeriodNsecs(4000), /*applyImmediately*/ false);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(13000));
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 8d9623d..51373c1 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -195,9 +195,7 @@
 
 TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) {
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(2);
-    mReactor.setIgnorePresentFences(true);
-
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(3);
     nsecs_t const newPeriod = 5000;
 
     mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
@@ -207,7 +205,7 @@
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(newPeriod, std::nullopt, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
@@ -232,7 +230,8 @@
 TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) {
     nsecs_t sampleTime = 0;
     nsecs_t const newPeriod = 5000;
-    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
+    auto modePtr = displayMode(newPeriod);
+    mReactor.onDisplayModeChanged(modePtr, false);
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -240,7 +239,9 @@
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 
-    mReactor.onDisplayModeChanged(displayMode(period), false);
+    modePtr = displayMode(period);
+    EXPECT_CALL(*mMockTracker, isCurrentMode(modePtr)).WillOnce(Return(true));
+    mReactor.onDisplayModeChanged(modePtr, false);
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 }
@@ -463,8 +464,7 @@
 
 TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) {
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(2);
-    mReactor.setIgnorePresentFences(true);
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(3);
 
     nsecs_t const newPeriod = 5000;
     mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
@@ -476,7 +476,7 @@
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(newPeriod, newPeriod, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) {
@@ -486,8 +486,7 @@
                          *mMockTracker, kPendingLimit, true /* supportKernelIdleTimer */);
 
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(4);
-    idleReactor.setIgnorePresentFences(true);
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(5);
 
     // First, set the same period, which should only be confirmed when we receive two
     // matching callbacks
@@ -512,7 +511,7 @@
     EXPECT_FALSE(idleReactor.addHwVsyncTimestamp(20000, 5000, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(idleReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(idleReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index 5bcce50..685d8f9 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -24,7 +24,7 @@
         DisplayModeId modeId, Fps displayRefreshRate, int32_t group = 0,
         ui::Size resolution = ui::Size(1920, 1080),
         PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
-    return DisplayMode::Builder(hal::HWConfigId(modeId.value()))
+    return DisplayMode::Builder(hal::HWConfigId(ftl::to_underlying(modeId)))
             .setId(modeId)
             .setPhysicalDisplayId(displayId)
             .setVsyncPeriod(displayRefreshRate.getPeriodNsecs())
@@ -45,8 +45,8 @@
 }
 
 inline DisplayModePtr createVrrDisplayMode(
-        DisplayModeId modeId, Fps displayRefreshRate, hal::VrrConfig vrrConfig, int32_t group = 0,
-        ui::Size resolution = ui::Size(1920, 1080),
+        DisplayModeId modeId, Fps displayRefreshRate, std::optional<hal::VrrConfig> vrrConfig,
+        int32_t group = 0, ui::Size resolution = ui::Size(1920, 1080),
         PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
     return createDisplayModeBuilder(modeId, displayRefreshRate, group, resolution, displayId)
             .setVrrConfig(std::move(vrrConfig))
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 7413235..602bdfc 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -53,7 +53,8 @@
     MOCK_METHOD(hal::Error, getRequests,
                 (hal::DisplayRequest *, (std::unordered_map<Layer *, hal::LayerRequest> *)),
                 (override));
-    MOCK_METHOD(hal::Error, getConnectionType, (ui::DisplayConnectionType *), (const, override));
+    MOCK_METHOD((ftl::Expected<ui::DisplayConnectionType, hal::Error>), getConnectionType, (),
+                (const, override));
     MOCK_METHOD(hal::Error, supportsDoze, (bool *), (const, override));
     MOCK_METHOD(hal::Error, getHdrCapabilities, (android::HdrCapabilities *), (const, override));
     MOCK_METHOD(hal::Error, getDisplayedContentSamplingAttributes,
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
index a088aab..ed1405b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
@@ -18,12 +18,21 @@
 
 #include "binder/Status.h"
 
+// FMQ library in IPower does questionable conversions
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
+#pragma clang diagnostic pop
+
 #include <gmock/gmock.h>
 
 using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::ChannelConfig;
 using aidl::android::hardware::power::IPower;
 using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionTag;
+
 using aidl::android::hardware::power::Mode;
 using android::binder::Status;
 
@@ -42,6 +51,14 @@
                  int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
                 (override));
     MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, createHintSessionWithConfig,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, SessionTag tag, SessionConfig* config,
+                 std::shared_ptr<IPowerHintSession>* _aidl_return),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel,
+                (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
     MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
deleted file mode 100644
index 364618d..0000000
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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 "binder/Status.h"
-
-#include <aidl/android/hardware/power/IPower.h>
-#include <gmock/gmock.h>
-
-using aidl::android::hardware::power::IPowerHintSession;
-using aidl::android::hardware::power::SessionHint;
-using aidl::android::hardware::power::SessionMode;
-using android::binder::Status;
-
-using namespace aidl::android::hardware::power;
-
-namespace android::Hwc2::mock {
-
-class MockIPowerHintSession : public IPowerHintSession {
-public:
-    MockIPowerHintSession();
-
-    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, pause, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, resume, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, close, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
-    MOCK_METHOD(bool, isRemote, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, reportActualWorkDuration, (const ::std::vector<WorkDuration>&),
-                (override));
-    MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector<int32_t>&), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override));
-};
-
-} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index 8e8eb1d..4efdfe8 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -36,10 +36,12 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, supportsGpuReporting, (), (override));
     MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
+    MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
     MOCK_METHOD(void, setHwcValidateTiming,
@@ -49,8 +51,8 @@
                 (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
                 (override));
     MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
-    MOCK_METHOD(void, setRequiresClientComposition,
-                (DisplayId displayId, bool requiresClientComposition), (override));
+    MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
+                (override));
     MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
     MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
                 (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
index 68fe3c5..af1862d 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -19,7 +19,10 @@
 #include <gmock/gmock.h>
 #include <scheduler/Time.h>
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <powermanager/PowerHalController.h>
+#pragma clang diagnostic pop
 
 namespace android {
 namespace hardware {
@@ -31,8 +34,6 @@
 
 namespace android::Hwc2::mock {
 
-using aidl::android::hardware::power::Boost;
-using aidl::android::hardware::power::Mode;
 using android::power::HalResult;
 
 class MockPowerHalController : public power::PowerHalController {
@@ -40,12 +41,22 @@
     MockPowerHalController();
     ~MockPowerHalController() override;
     MOCK_METHOD(void, init, (), (override));
-    MOCK_METHOD(HalResult<void>, setBoost, (Boost, int32_t), (override));
-    MOCK_METHOD(HalResult<void>, setMode, (Mode, bool), (override));
-    MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
+    MOCK_METHOD(HalResult<void>, setBoost, (aidl::android::hardware::power::Boost, int32_t),
+                (override));
+    MOCK_METHOD(HalResult<void>, setMode, (aidl::android::hardware::power::Mode, bool), (override));
+    MOCK_METHOD(HalResult<std::shared_ptr<android::power::PowerHintSessionWrapper>>,
                 createHintSession, (int32_t, int32_t, const std::vector<int32_t>&, int64_t),
                 (override));
+    MOCK_METHOD(HalResult<std::shared_ptr<android::power::PowerHintSessionWrapper>>,
+                createHintSessionWithConfig,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, aidl::android::hardware::power::SessionTag tag,
+                 aidl::android::hardware::power::SessionConfig* config),
+                (override));
     MOCK_METHOD(HalResult<int64_t>, getHintSessionPreferredRate, (), (override));
+    MOCK_METHOD(HalResult<aidl::android::hardware::power::ChannelConfig>, getSessionChannel,
+                (int tgid, int uid), (override));
+    MOCK_METHOD(HalResult<void>, closeSessionChannel, (int tgid, int uid), (override));
 };
 
 } // namespace android::Hwc2::mock
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp
similarity index 75%
rename from services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp
rename to services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp
index 770bc15..d383283 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "mock/DisplayHardware/MockIPowerHintSession.h"
+#include "mock/DisplayHardware/MockPowerHintSessionWrapper.h"
 
 namespace android::Hwc2::mock {
 
 // Explicit default instantiation is recommended.
-MockIPowerHintSession::MockIPowerHintSession() = default;
+MockPowerHintSessionWrapper::MockPowerHintSessionWrapper()
+      : power::PowerHintSessionWrapper(nullptr) {}
 
 } // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h
new file mode 100644
index 0000000..bc6950c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 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 "binder/Status.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <aidl/android/hardware/power/IPower.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+#pragma clang diagnostic pop
+
+#include <gmock/gmock.h>
+
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionMode;
+using android::binder::Status;
+
+using namespace aidl::android::hardware::power;
+
+namespace android::Hwc2::mock {
+
+class MockPowerHintSessionWrapper : public power::PowerHintSessionWrapper {
+public:
+    MockPowerHintSessionWrapper();
+
+    MOCK_METHOD(power::HalResult<void>, updateTargetWorkDuration, (int64_t), (override));
+    MOCK_METHOD(power::HalResult<void>, reportActualWorkDuration,
+                (const ::std::vector<WorkDuration>&), (override));
+    MOCK_METHOD(power::HalResult<void>, pause, (), (override));
+    MOCK_METHOD(power::HalResult<void>, resume, (), (override));
+    MOCK_METHOD(power::HalResult<void>, close, (), (override));
+    MOCK_METHOD(power::HalResult<void>, sendHint, (SessionHint), (override));
+    MOCK_METHOD(power::HalResult<void>, setThreads, (const ::std::vector<int32_t>&), (override));
+    MOCK_METHOD(power::HalResult<void>, setMode, (SessionMode, bool), (override));
+    MOCK_METHOD(power::HalResult<SessionConfig>, getSessionConfig, (), (override));
+};
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
index a71e82c..7b18a82 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
@@ -18,12 +18,15 @@
 
 #include <android/gui/DisplayModeSpecs.h>
 
+#include "DisplayHardware/DisplayMode.h"
+
 namespace android::mock {
 
-inline gui::DisplayModeSpecs createDisplayModeSpecs(int32_t defaultMode, bool allowGroupSwitching,
-                                                    float minFps, float maxFps) {
+inline gui::DisplayModeSpecs createDisplayModeSpecs(DisplayModeId defaultMode,
+                                                    bool allowGroupSwitching, float minFps,
+                                                    float maxFps) {
     gui::DisplayModeSpecs specs;
-    specs.defaultMode = defaultMode;
+    specs.defaultMode = ftl::to_underlying(defaultMode);
     specs.allowGroupSwitching = allowGroupSwitching;
     specs.primaryRanges.physical.min = minFps;
     specs.primaryRanges.physical.max = maxFps;
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 866af3b..8dd1a34 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -55,10 +55,13 @@
                 (override));
     MOCK_METHOD(void, requestNextVsync, (const sp<android::EventThreadConnection>&), (override));
     MOCK_METHOD(VsyncEventData, getLatestVsyncEventData,
-                (const sp<android::EventThreadConnection>&), (const, override));
+                (const sp<android::EventThreadConnection>&, nsecs_t), (const, override));
     MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
     MOCK_METHOD(void, pauseVsyncCallback, (bool));
     MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
+    MOCK_METHOD(void, onHdcpLevelsChanged,
+                (PhysicalDisplayId displayId, int32_t connectedLevel, int32_t maxLevel),
+                (override));
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 4204aa0..002fa9f 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -40,7 +40,7 @@
     MOCK_CONST_METHOD0(getType, const char*());
     MOCK_METHOD0(getFrameSelectionPriority, int32_t());
     MOCK_CONST_METHOD0(isVisible, bool());
-    MOCK_METHOD1(createClone, sp<Layer>(uint32_t));
+    MOCK_METHOD0(createClone, sp<Layer>());
     MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate());
     MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility, scheduler::FrameRateCompatibility());
     MOCK_CONST_METHOD0(getOwnerUid, uid_t());
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 22b2ccc..dec5fa5 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -28,6 +28,9 @@
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
     MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
     MOCK_METHOD(void, onChoreographerAttached, (), (override));
+    MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps),
+                (override));
+    MOCK_METHOD(void, onCommitNotComposited, (PhysicalDisplayId), (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
@@ -36,6 +39,8 @@
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
     void onChoreographerAttached() override {}
+    void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
+    void onCommitNotComposited(PhysicalDisplayId) override {}
 };
 
 } // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h
index dc32ff9..1dc2ef4 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h
@@ -29,8 +29,10 @@
 
     MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
     MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
-    MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
-    MOCK_METHOD(scheduler::ScheduleResult, update, (CallbackToken, ScheduleTiming), (override));
+    MOCK_METHOD(std::optional<scheduler::ScheduleResult>, schedule, (CallbackToken, ScheduleTiming),
+                (override));
+    MOCK_METHOD(std::optional<scheduler::ScheduleResult>, update, (CallbackToken, ScheduleTiming),
+                (override));
     MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken token), (override));
     MOCK_METHOD(void, dump, (std::string&), (const, override));
 };
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index e588bb9..4f44d1b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -28,17 +28,19 @@
     ~VSyncTracker() override;
 
     MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override));
-    MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t), (const, override));
+    MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional<nsecs_t>),
+                (override));
     MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override));
     MOCK_METHOD(Period, minFramePeriod, (), (const, override));
     MOCK_METHOD(void, resetModel, (), (override));
     MOCK_METHOD(bool, needsMoreSamples, (), (const, override));
-    MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override));
+    MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override));
     MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull<DisplayModePtr>), (override));
-    MOCK_METHOD(void, setRenderRate, (Fps), (override));
+    MOCK_METHOD(void, setRenderRate, (Fps, bool), (override));
     MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override));
     MOCK_METHOD(void, onFrameMissed, (TimePoint), (override));
     MOCK_METHOD(void, dump, (std::string&), (const, override));
+    MOCK_METHOD(bool, isCurrentMode, (const ftl::NonNull<DisplayModePtr>&), (const, override));
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
deleted file mode 100644
index b48529f..0000000
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-
-#include "Scheduler/VSyncTracker.h"
-
-namespace android::scheduler::mock {
-
-struct VsyncTrackerCallback final : IVsyncTrackerCallback {
-    MOCK_METHOD(void, onVsyncGenerated, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps), (override));
-};
-
-struct NoOpVsyncTrackerCallback final : IVsyncTrackerCallback {
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override{};
-};
-} // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h
index 38c422a..07916b6 100644
--- a/services/surfaceflinger/tests/utils/ColorUtils.h
+++ b/services/surfaceflinger/tests/utils/ColorUtils.h
@@ -33,10 +33,6 @@
     static const Color WHITE;
     static const Color BLACK;
     static const Color TRANSPARENT;
-
-    half3 toHalf3() { return half3{r / 255.0f, g / 255.0f, b / 255.0f}; }
-
-    half4 toHalf4() { return half4{r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; }
 };
 
 const Color Color::RED{255, 0, 0, 255};
@@ -85,14 +81,6 @@
         }
         color = ret;
     }
-
-    static half3 toHalf3(const Color& color) {
-        return half3{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f};
-    }
-
-    static half4 toHalf4(const Color& color) {
-        return half4{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f};
-    }
 };
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/vsync/Android.bp b/services/surfaceflinger/tests/vsync/Android.bp
index bae9796..c7daa88 100644
--- a/services/surfaceflinger/tests/vsync/Android.bp
+++ b/services/surfaceflinger/tests/vsync/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_binary {
@@ -33,6 +34,6 @@
         "libgui",
         "libui",
         "libutils",
-    ]
+    ],
 
 }
diff --git a/services/surfaceflinger/tests/waitforvsync/Android.bp b/services/surfaceflinger/tests/waitforvsync/Android.bp
index ffed4d7..734fc20 100644
--- a/services/surfaceflinger/tests/waitforvsync/Android.bp
+++ b/services/surfaceflinger/tests/waitforvsync/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
 }
 
 cc_binary {
@@ -31,5 +32,5 @@
     ],
     shared_libs: [
         "libcutils",
-    ]
+    ],
 }
diff --git a/services/vibratorservice/TEST_MAPPING b/services/vibratorservice/TEST_MAPPING
index 63a2bd0..af48673 100644
--- a/services/vibratorservice/TEST_MAPPING
+++ b/services/vibratorservice/TEST_MAPPING
@@ -1,13 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "libvibratorservice_test",
-      "options": [
-        // TODO(b/293603710): Fix flakiness
-        {
-          "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleRunsOnlyAfterDelay"
-        }
-      ]
+      "name": "libvibratorservice_test"
     }
   ],
   "postsubmit": [
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 63ecaec..f10ba44 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -56,75 +56,6 @@
 
 // -------------------------------------------------------------------------------------------------
 
-const constexpr char* STATUS_T_ERROR_MESSAGE_PREFIX = "status_t = ";
-const constexpr char* STATUS_V_1_0_ERROR_MESSAGE_PREFIX =
-        "android::hardware::vibrator::V1_0::Status = ";
-
-template <typename T>
-HalResult<T> HalResult<T>::fromStatus(V1_0::Status status, T data) {
-    switch (status) {
-        case V1_0::Status::OK:
-            return HalResult<T>::ok(data);
-        case V1_0::Status::UNSUPPORTED_OPERATION:
-            return HalResult<T>::unsupported();
-        default:
-            return HalResult<T>::failed(STATUS_V_1_0_ERROR_MESSAGE_PREFIX + toString(status));
-    }
-}
-
-template <typename T>
-template <typename R>
-HalResult<T> HalResult<T>::fromReturn(hardware::Return<R>& ret, T data) {
-    return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed(ret.description());
-}
-
-template <typename T>
-template <typename R>
-HalResult<T> HalResult<T>::fromReturn(hardware::Return<R>& ret, V1_0::Status status, T data) {
-    return ret.isOk() ? HalResult<T>::fromStatus(status, data)
-                      : HalResult<T>::failed(ret.description());
-}
-
-// -------------------------------------------------------------------------------------------------
-
-HalResult<void> HalResult<void>::fromStatus(status_t status) {
-    if (status == android::OK) {
-        return HalResult<void>::ok();
-    }
-    return HalResult<void>::failed(STATUS_T_ERROR_MESSAGE_PREFIX + statusToString(status));
-}
-
-HalResult<void> HalResult<void>::fromStatus(binder::Status status) {
-    if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
-        status.transactionError() == android::UNKNOWN_TRANSACTION) {
-        // UNKNOWN_TRANSACTION means the HAL implementation is an older version, so this is
-        // the same as the operation being unsupported by this HAL. Should not retry.
-        return HalResult<void>::unsupported();
-    }
-    if (status.isOk()) {
-        return HalResult<void>::ok();
-    }
-    return HalResult<void>::failed(std::string(status.toString8().c_str()));
-}
-
-HalResult<void> HalResult<void>::fromStatus(V1_0::Status status) {
-    switch (status) {
-        case V1_0::Status::OK:
-            return HalResult<void>::ok();
-        case V1_0::Status::UNSUPPORTED_OPERATION:
-            return HalResult<void>::unsupported();
-        default:
-            return HalResult<void>::failed(STATUS_V_1_0_ERROR_MESSAGE_PREFIX + toString(status));
-    }
-}
-
-template <typename R>
-HalResult<void> HalResult<void>::fromReturn(hardware::Return<R>& ret) {
-    return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
-}
-
-// -------------------------------------------------------------------------------------------------
-
 Info HalWrapper::getInfo() {
     getCapabilities();
     getPrimitiveDurations();
@@ -269,7 +200,7 @@
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> AidlHalWrapper::ping() {
-    return HalResult<void>::fromStatus(IInterface::asBinder(getHal())->pingBinder());
+    return HalResultFactory::fromStatus(IInterface::asBinder(getHal())->pingBinder());
 }
 
 void AidlHalWrapper::tryReconnect() {
@@ -291,7 +222,7 @@
             static_cast<int32_t>(capabilities.value() & Capabilities::ON_CALLBACK);
     auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
 
-    auto ret = HalResult<void>::fromStatus(getHal()->on(timeout.count(), cb));
+    auto ret = HalResultFactory::fromStatus(getHal()->on(timeout.count(), cb));
     if (!supportsCallback && ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, timeout);
     }
@@ -300,23 +231,23 @@
 }
 
 HalResult<void> AidlHalWrapper::off() {
-    return HalResult<void>::fromStatus(getHal()->off());
+    return HalResultFactory::fromStatus(getHal()->off());
 }
 
 HalResult<void> AidlHalWrapper::setAmplitude(float amplitude) {
-    return HalResult<void>::fromStatus(getHal()->setAmplitude(amplitude));
+    return HalResultFactory::fromStatus(getHal()->setAmplitude(amplitude));
 }
 
 HalResult<void> AidlHalWrapper::setExternalControl(bool enabled) {
-    return HalResult<void>::fromStatus(getHal()->setExternalControl(enabled));
+    return HalResultFactory::fromStatus(getHal()->setExternalControl(enabled));
 }
 
 HalResult<void> AidlHalWrapper::alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) {
-    return HalResult<void>::fromStatus(getHal()->alwaysOnEnable(id, effect, strength));
+    return HalResultFactory::fromStatus(getHal()->alwaysOnEnable(id, effect, strength));
 }
 
 HalResult<void> AidlHalWrapper::alwaysOnDisable(int32_t id) {
-    return HalResult<void>::fromStatus(getHal()->alwaysOnDisable(id));
+    return HalResultFactory::fromStatus(getHal()->alwaysOnDisable(id));
 }
 
 HalResult<milliseconds> AidlHalWrapper::performEffect(
@@ -330,7 +261,7 @@
     auto result = getHal()->perform(effect, strength, cb, &lengthMs);
     milliseconds length = milliseconds(lengthMs);
 
-    auto ret = HalResult<milliseconds>::fromStatus(result, length);
+    auto ret = HalResultFactory::fromStatus<milliseconds>(result, length);
     if (!supportsCallback && ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, length);
     }
@@ -357,38 +288,40 @@
         duration += milliseconds(effect.delayMs);
     }
 
-    return HalResult<milliseconds>::fromStatus(getHal()->compose(primitives, cb), duration);
+    return HalResultFactory::fromStatus<milliseconds>(getHal()->compose(primitives, cb), duration);
 }
 
 HalResult<void> AidlHalWrapper::performPwleEffect(const std::vector<PrimitivePwle>& primitives,
                                                   const std::function<void()>& completionCallback) {
     // This method should always support callbacks, so no need to double check.
     auto cb = new HalCallbackWrapper(completionCallback);
-    return HalResult<void>::fromStatus(getHal()->composePwle(primitives, cb));
+    return HalResultFactory::fromStatus(getHal()->composePwle(primitives, cb));
 }
 
 HalResult<Capabilities> AidlHalWrapper::getCapabilitiesInternal() {
     int32_t capabilities = 0;
     auto result = getHal()->getCapabilities(&capabilities);
-    return HalResult<Capabilities>::fromStatus(result, static_cast<Capabilities>(capabilities));
+    return HalResultFactory::fromStatus<Capabilities>(result,
+                                                      static_cast<Capabilities>(capabilities));
 }
 
 HalResult<std::vector<Effect>> AidlHalWrapper::getSupportedEffectsInternal() {
     std::vector<Effect> supportedEffects;
     auto result = getHal()->getSupportedEffects(&supportedEffects);
-    return HalResult<std::vector<Effect>>::fromStatus(result, supportedEffects);
+    return HalResultFactory::fromStatus<std::vector<Effect>>(result, supportedEffects);
 }
 
 HalResult<std::vector<Braking>> AidlHalWrapper::getSupportedBrakingInternal() {
     std::vector<Braking> supportedBraking;
     auto result = getHal()->getSupportedBraking(&supportedBraking);
-    return HalResult<std::vector<Braking>>::fromStatus(result, supportedBraking);
+    return HalResultFactory::fromStatus<std::vector<Braking>>(result, supportedBraking);
 }
 
 HalResult<std::vector<CompositePrimitive>> AidlHalWrapper::getSupportedPrimitivesInternal() {
     std::vector<CompositePrimitive> supportedPrimitives;
     auto result = getHal()->getSupportedPrimitives(&supportedPrimitives);
-    return HalResult<std::vector<CompositePrimitive>>::fromStatus(result, supportedPrimitives);
+    return HalResultFactory::fromStatus<std::vector<CompositePrimitive>>(result,
+                                                                         supportedPrimitives);
 }
 
 HalResult<std::vector<milliseconds>> AidlHalWrapper::getPrimitiveDurationsInternal(
@@ -408,7 +341,7 @@
         }
         int32_t duration = 0;
         auto result = getHal()->getPrimitiveDuration(primitive, &duration);
-        auto halResult = HalResult<int32_t>::fromStatus(result, duration);
+        auto halResult = HalResultFactory::fromStatus<int32_t>(result, duration);
         if (halResult.isUnsupported()) {
             // Should not happen, supported primitives should always support requesting duration.
             ALOGE("Supported primitive %zu returned unsupported for getPrimitiveDuration",
@@ -427,55 +360,55 @@
 HalResult<milliseconds> AidlHalWrapper::getPrimitiveDelayMaxInternal() {
     int32_t delay = 0;
     auto result = getHal()->getCompositionDelayMax(&delay);
-    return HalResult<milliseconds>::fromStatus(result, milliseconds(delay));
+    return HalResultFactory::fromStatus<milliseconds>(result, milliseconds(delay));
 }
 
 HalResult<milliseconds> AidlHalWrapper::getPrimitiveDurationMaxInternal() {
     int32_t delay = 0;
     auto result = getHal()->getPwlePrimitiveDurationMax(&delay);
-    return HalResult<milliseconds>::fromStatus(result, milliseconds(delay));
+    return HalResultFactory::fromStatus<milliseconds>(result, milliseconds(delay));
 }
 
 HalResult<int32_t> AidlHalWrapper::getCompositionSizeMaxInternal() {
     int32_t size = 0;
     auto result = getHal()->getCompositionSizeMax(&size);
-    return HalResult<int32_t>::fromStatus(result, size);
+    return HalResultFactory::fromStatus<int32_t>(result, size);
 }
 
 HalResult<int32_t> AidlHalWrapper::getPwleSizeMaxInternal() {
     int32_t size = 0;
     auto result = getHal()->getPwleCompositionSizeMax(&size);
-    return HalResult<int32_t>::fromStatus(result, size);
+    return HalResultFactory::fromStatus<int32_t>(result, size);
 }
 
 HalResult<float> AidlHalWrapper::getMinFrequencyInternal() {
     float minFrequency = 0;
     auto result = getHal()->getFrequencyMinimum(&minFrequency);
-    return HalResult<float>::fromStatus(result, minFrequency);
+    return HalResultFactory::fromStatus<float>(result, minFrequency);
 }
 
 HalResult<float> AidlHalWrapper::getResonantFrequencyInternal() {
     float f0 = 0;
     auto result = getHal()->getResonantFrequency(&f0);
-    return HalResult<float>::fromStatus(result, f0);
+    return HalResultFactory::fromStatus<float>(result, f0);
 }
 
 HalResult<float> AidlHalWrapper::getFrequencyResolutionInternal() {
     float frequencyResolution = 0;
     auto result = getHal()->getFrequencyResolution(&frequencyResolution);
-    return HalResult<float>::fromStatus(result, frequencyResolution);
+    return HalResultFactory::fromStatus<float>(result, frequencyResolution);
 }
 
 HalResult<float> AidlHalWrapper::getQFactorInternal() {
     float qFactor = 0;
     auto result = getHal()->getQFactor(&qFactor);
-    return HalResult<float>::fromStatus(result, qFactor);
+    return HalResultFactory::fromStatus<float>(result, qFactor);
 }
 
 HalResult<std::vector<float>> AidlHalWrapper::getMaxAmplitudesInternal() {
     std::vector<float> amplitudes;
     auto result = getHal()->getBandwidthAmplitudeMap(&amplitudes);
-    return HalResult<std::vector<float>>::fromStatus(result, amplitudes);
+    return HalResultFactory::fromStatus<std::vector<float>>(result, amplitudes);
 }
 
 sp<Aidl::IVibrator> AidlHalWrapper::getHal() {
@@ -488,7 +421,7 @@
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::ping() {
     auto result = getHal()->ping();
-    return HalResult<void>::fromReturn(result);
+    return HalResultFactory::fromReturn(result);
 }
 
 template <typename I>
@@ -504,7 +437,7 @@
 HalResult<void> HidlHalWrapper<I>::on(milliseconds timeout,
                                       const std::function<void()>& completionCallback) {
     auto result = getHal()->on(timeout.count());
-    auto ret = HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    auto ret = HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
     if (ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, timeout);
     }
@@ -514,14 +447,14 @@
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::off() {
     auto result = getHal()->off();
-    return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    return HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::setAmplitude(float amplitude) {
     uint8_t amp = static_cast<uint8_t>(amplitude * std::numeric_limits<uint8_t>::max());
     auto result = getHal()->setAmplitude(amp);
-    return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    return HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
 template <typename I>
@@ -547,7 +480,7 @@
     hardware::Return<bool> result = getHal()->supportsAmplitudeControl();
     Capabilities capabilities =
             result.withDefault(false) ? Capabilities::AMPLITUDE_CONTROL : Capabilities::NONE;
-    return HalResult<Capabilities>::fromReturn(result, capabilities);
+    return HalResultFactory::fromReturn<Capabilities>(result, capabilities);
 }
 
 template <typename I>
@@ -566,7 +499,7 @@
     auto result = std::invoke(performFn, handle, effect, effectStrength, effectCallback);
     milliseconds length = milliseconds(lengthMs);
 
-    auto ret = HalResult<milliseconds>::fromReturn(result, status, length);
+    auto ret = HalResultFactory::fromReturn<milliseconds>(result, status, length);
     if (ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, length);
     }
@@ -638,7 +571,7 @@
 
 HalResult<void> HidlHalWrapperV1_3::setExternalControl(bool enabled) {
     auto result = getHal()->setExternalControl(static_cast<uint32_t>(enabled));
-    return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    return HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
 HalResult<milliseconds> HidlHalWrapperV1_3::performEffect(
@@ -671,7 +604,7 @@
     sp<V1_3::IVibrator> hal = getHal();
     auto amplitudeResult = hal->supportsAmplitudeControl();
     if (!amplitudeResult.isOk()) {
-        return HalResult<Capabilities>::fromReturn(amplitudeResult, capabilities);
+        return HalResultFactory::fromReturn<Capabilities>(amplitudeResult, capabilities);
     }
 
     auto externalControlResult = hal->supportsExternalControl();
@@ -686,7 +619,7 @@
         }
     }
 
-    return HalResult<Capabilities>::fromReturn(externalControlResult, capabilities);
+    return HalResultFactory::fromReturn<Capabilities>(externalControlResult, capabilities);
 }
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/vibratorservice/VibratorManagerHalController.cpp b/services/vibratorservice/VibratorManagerHalController.cpp
index 0df0bfa..aa5b7fc 100644
--- a/services/vibratorservice/VibratorManagerHalController.cpp
+++ b/services/vibratorservice/VibratorManagerHalController.cpp
@@ -46,8 +46,6 @@
 HalResult<T> ManagerHalController::processHalResult(HalResult<T> result, const char* functionName) {
     if (result.isFailed()) {
         ALOGE("VibratorManager HAL %s failed: %s", functionName, result.errorMessage());
-        std::lock_guard<std::mutex> lock(mConnectedHalMutex);
-        mConnectedHal->tryReconnect();
     }
     return result;
 }
@@ -70,12 +68,16 @@
         hal = mConnectedHal;
     }
 
-    HalResult<T> ret = processHalResult(halFn(hal), functionName);
-    for (int i = 0; i < MAX_RETRIES && ret.isFailed(); i++) {
-        ret = processHalResult(halFn(hal), functionName);
+    HalResult<T> result = processHalResult(halFn(hal), functionName);
+    for (int i = 0; i < MAX_RETRIES && result.shouldRetry(); i++) {
+        {
+            std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+            mConnectedHal->tryReconnect();
+        }
+        result = processHalResult(halFn(hal), functionName);
     }
 
-    return ret;
+    return result;
 }
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/vibratorservice/VibratorManagerHalWrapper.cpp b/services/vibratorservice/VibratorManagerHalWrapper.cpp
index 6e660e7..1341266 100644
--- a/services/vibratorservice/VibratorManagerHalWrapper.cpp
+++ b/services/vibratorservice/VibratorManagerHalWrapper.cpp
@@ -55,8 +55,8 @@
         return HalResult<std::shared_ptr<HalController>>::ok(mController);
     }
     // Controller.init did not connect to any vibrator HAL service, so the device has no vibrator.
-    return HalResult<std::shared_ptr<HalController>>::failed(MISSING_VIBRATOR_MESSAGE_PREFIX +
-                                                             std::to_string(id));
+    return HalResult<std::shared_ptr<HalController>>::failed(
+            (MISSING_VIBRATOR_MESSAGE_PREFIX + std::to_string(id)).c_str());
 }
 
 HalResult<void> LegacyManagerHalWrapper::prepareSynced(const std::vector<int32_t>&) {
@@ -75,10 +75,10 @@
 
 std::shared_ptr<HalWrapper> AidlManagerHalWrapper::connectToVibrator(
         int32_t vibratorId, std::shared_ptr<CallbackScheduler> callbackScheduler) {
-    std::function<HalResult<sp<Aidl::IVibrator>>()> reconnectFn = [=]() {
+    std::function<HalResult<sp<Aidl::IVibrator>>()> reconnectFn = [=, this]() {
         sp<Aidl::IVibrator> vibrator;
         auto result = this->getHal()->getVibrator(vibratorId, &vibrator);
-        return HalResult<sp<Aidl::IVibrator>>::fromStatus(result, vibrator);
+        return HalResultFactory::fromStatus<sp<Aidl::IVibrator>>(result, vibrator);
     };
     auto result = reconnectFn();
     if (!result.isOk()) {
@@ -88,12 +88,12 @@
     if (!vibrator) {
         return nullptr;
     }
-    return std::move(std::make_unique<AidlHalWrapper>(std::move(callbackScheduler),
-                                                      std::move(vibrator), reconnectFn));
+    return std::make_unique<AidlHalWrapper>(std::move(callbackScheduler), std::move(vibrator),
+                                            reconnectFn);
 }
 
 HalResult<void> AidlManagerHalWrapper::ping() {
-    return HalResult<void>::fromStatus(IInterface::asBinder(getHal())->pingBinder());
+    return HalResultFactory::fromStatus(IInterface::asBinder(getHal())->pingBinder());
 }
 
 void AidlManagerHalWrapper::tryReconnect() {
@@ -112,8 +112,8 @@
     }
     int32_t cap = 0;
     auto result = getHal()->getCapabilities(&cap);
-    auto ret = HalResult<ManagerCapabilities>::fromStatus(result,
-                                                          static_cast<ManagerCapabilities>(cap));
+    auto capabilities = static_cast<ManagerCapabilities>(cap);
+    auto ret = HalResultFactory::fromStatus<ManagerCapabilities>(result, capabilities);
     if (ret.isOk()) {
         // Cache copy of returned value.
         mCapabilities.emplace(ret.value());
@@ -129,7 +129,7 @@
     }
     std::vector<int32_t> ids;
     auto result = getHal()->getVibratorIds(&ids);
-    auto ret = HalResult<std::vector<int32_t>>::fromStatus(result, ids);
+    auto ret = HalResultFactory::fromStatus<std::vector<int32_t>>(result, ids);
     if (ret.isOk()) {
         // Cache copy of returned value and the individual controllers.
         mVibratorIds.emplace(ret.value());
@@ -152,12 +152,12 @@
     if (it != mVibrators.end()) {
         return HalResult<std::shared_ptr<HalController>>::ok(it->second);
     }
-    return HalResult<std::shared_ptr<HalController>>::failed(MISSING_VIBRATOR_MESSAGE_PREFIX +
-                                                             std::to_string(id));
+    return HalResult<std::shared_ptr<HalController>>::failed(
+            (MISSING_VIBRATOR_MESSAGE_PREFIX + std::to_string(id)).c_str());
 }
 
 HalResult<void> AidlManagerHalWrapper::prepareSynced(const std::vector<int32_t>& ids) {
-    auto ret = HalResult<void>::fromStatus(getHal()->prepareSynced(ids));
+    auto ret = HalResultFactory::fromStatus(getHal()->prepareSynced(ids));
     if (ret.isOk()) {
         // Force reload of all vibrator controllers that were prepared for a sync operation here.
         // This will trigger calls to getVibrator(id) on each controller, so they can use the
@@ -179,11 +179,11 @@
     bool supportsCallback = capabilities.isOk() &&
             static_cast<int32_t>(capabilities.value() & ManagerCapabilities::TRIGGER_CALLBACK);
     auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
-    return HalResult<void>::fromStatus(getHal()->triggerSynced(cb));
+    return HalResultFactory::fromStatus(getHal()->triggerSynced(cb));
 }
 
 HalResult<void> AidlManagerHalWrapper::cancelSynced() {
-    auto ret = HalResult<void>::fromStatus(getHal()->cancelSynced());
+    auto ret = HalResultFactory::fromStatus(getHal()->cancelSynced());
     if (ret.isOk()) {
         // Force reload of all vibrator controllers that were prepared for a sync operation before.
         // This will trigger calls to getVibrator(id) on each controller, so they can use the
diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
index 971a0b9..9b30337 100644
--- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
+++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
@@ -17,7 +17,9 @@
 #define LOG_TAG "VibratorHalControllerBenchmarks"
 
 #include <benchmark/benchmark.h>
+#include <binder/ProcessState.h>
 #include <vibratorservice/VibratorHalController.h>
+#include <future>
 
 using ::android::enum_range;
 using ::android::hardware::vibrator::CompositeEffect;
@@ -30,14 +32,99 @@
 using ::benchmark::State;
 using ::benchmark::internal::Benchmark;
 
+using std::chrono::milliseconds;
+
 using namespace android;
 using namespace std::chrono_literals;
 
+// Fixed number of iterations for benchmarks that trigger a vibration on the loop.
+// They require slow cleanup to ensure a stable state on each run and less noisy metrics.
+static constexpr auto VIBRATION_ITERATIONS = 500;
+
+// Timeout to wait for vibration callback completion.
+static constexpr auto VIBRATION_CALLBACK_TIMEOUT = 100ms;
+
+// Max duration the vibrator can be turned on, in milliseconds.
+static constexpr auto MAX_ON_DURATION_MS = milliseconds(UINT16_MAX);
+
+// Helper to wait for the vibrator to become idle between vibrate bench iterations.
+class HalCallback {
+public:
+    HalCallback(std::function<void()>&& waitFn, std::function<void()>&& completeFn)
+          : mWaitFn(std::move(waitFn)), mCompleteFn(std::move(completeFn)) {}
+    ~HalCallback() = default;
+
+    std::function<void()> completeFn() const { return mCompleteFn; }
+
+    void waitForComplete() const { mWaitFn(); }
+
+private:
+    std::function<void()> mWaitFn;
+    std::function<void()> mCompleteFn;
+};
+
+// Helper for vibration callbacks, kept by the Fixture until all pending callbacks are done.
+class HalCallbacks {
+public:
+    HalCallback next() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        auto id = mCurrentId++;
+        mPendingPromises[id] = std::promise<void>();
+        mPendingFutures[id] = mPendingPromises[id].get_future(); // Can only be called once.
+        return HalCallback([&, id]() { waitForComplete(id); }, [&, id]() { onComplete(id); });
+    }
+
+    void onComplete(int32_t id) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        auto promise = mPendingPromises.find(id);
+        if (promise != mPendingPromises.end()) {
+            promise->second.set_value();
+            mPendingPromises.erase(promise);
+        }
+    }
+
+    void waitForComplete(int32_t id) {
+        // Wait until the HAL has finished processing previous vibration before starting a new one,
+        // so the HAL state is consistent on each run and metrics are less noisy. Some of the newest
+        // HAL implementations are waiting on previous vibration cleanup and might be significantly
+        // slower, so make sure we measure vibrations on a clean slate.
+        if (mPendingFutures[id].wait_for(VIBRATION_CALLBACK_TIMEOUT) == std::future_status::ready) {
+            mPendingFutures.erase(id);
+        }
+    }
+
+    void waitForPending() {
+        // Wait for pending callbacks from the test, possibly skipped with error.
+        for (auto& [id, future] : mPendingFutures) {
+            future.wait_for(VIBRATION_CALLBACK_TIMEOUT);
+        }
+        mPendingFutures.clear();
+        {
+            std::unique_lock<std::mutex> lock(mMutex);
+            mPendingPromises.clear();
+        }
+    }
+
+private:
+    std::mutex mMutex;
+    std::map<int32_t, std::promise<void>> mPendingPromises GUARDED_BY(mMutex);
+    std::map<int32_t, std::future<void>> mPendingFutures;
+    int32_t mCurrentId;
+};
+
 class VibratorBench : public Fixture {
 public:
-    void SetUp(State& /*state*/) override { mController.init(); }
+    void SetUp(State& /*state*/) override {
+        android::ProcessState::self()->setThreadPoolMaxThreadCount(1);
+        android::ProcessState::self()->startThreadPool();
+        mController.init();
+    }
 
-    void TearDown(State& state) override { turnVibratorOff(state); }
+    void TearDown(State& /*state*/) override {
+        turnVibratorOff();
+        disableExternalControl();
+        mCallbacks.waitForPending();
+    }
 
     static void DefaultConfig(Benchmark* b) { b->Unit(kMicrosecond); }
 
@@ -47,38 +134,59 @@
 
 protected:
     vibrator::HalController mController;
+    HalCallbacks mCallbacks;
+
+    static void SlowBenchConfig(Benchmark* b) { b->Iterations(VIBRATION_ITERATIONS); }
 
     auto getOtherArg(const State& state, std::size_t index) const { return state.range(index + 0); }
 
-    bool hasCapabilities(vibrator::Capabilities&& query, State& state) {
+    vibrator::HalResult<void> turnVibratorOff() {
+        return mController.doWithRetry<void>([](auto hal) { return hal->off(); }, "off");
+    }
+
+    vibrator::HalResult<void> disableExternalControl() {
+        auto disableExternalControlFn = [](auto hal) { return hal->setExternalControl(false); };
+        return mController.doWithRetry<void>(disableExternalControlFn, "setExternalControl false");
+    }
+
+    bool shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities query, State& state) {
         auto result = mController.getInfo().capabilities;
         if (result.isFailed()) {
             state.SkipWithError(result.errorMessage());
-            return false;
+            return true;
         }
         if (!result.isOk()) {
-            return false;
+            state.SkipWithMessage("capability result is unsupported");
+            return true;
         }
-        return (result.value() & query) == query;
-    }
-
-    void turnVibratorOff(State& state) {
-        checkHalResult(halCall<void>(mController, [](auto hal) { return hal->off(); }), state);
+        if ((result.value() & query) != query) {
+            state.SkipWithMessage("missing capability");
+            return true;
+        }
+        return false;
     }
 
     template <class R>
-    bool checkHalResult(const vibrator::HalResult<R>& result, State& state) {
+    bool shouldSkipWithError(const vibrator::HalFunction<vibrator::HalResult<R>>& halFn,
+                             const char* label, State& state) {
+        return shouldSkipWithError(mController.doWithRetry<R>(halFn, label), state);
+    }
+
+    template <class R>
+    bool shouldSkipWithError(const vibrator::HalResult<R>& result, State& state) {
         if (result.isFailed()) {
             state.SkipWithError(result.errorMessage());
-            return false;
+            return true;
         }
-        return true;
+        return false;
     }
+};
 
-    template <class R>
-    vibrator::HalResult<R> halCall(vibrator::HalController& controller,
-                                   const vibrator::HalFunction<vibrator::HalResult<R>>& halFn) {
-        return controller.doWithRetry<R>(halFn, "benchmark");
+class SlowVibratorBench : public VibratorBench {
+public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
     }
 };
 
@@ -91,25 +199,32 @@
 
 BENCHMARK_WRAPPER(VibratorBench, init, {
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
         vibrator::HalController controller;
         state.ResumeTiming();
+
+        // Test
         controller.init();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, initCached, {
+    // First call to cache values.
+    mController.init();
+
     for (auto _ : state) {
         mController.init();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, ping, {
+    auto pingFn = [](auto hal) { return hal->ping(); };
+
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<void>(mController, [](auto hal) { return hal->ping(); });
-        state.PauseTiming();
-        checkHalResult(ret, state);
+        if (shouldSkipWithError<void>(pingFn, "ping", state)) {
+            return;
+        }
     }
 });
 
@@ -119,159 +234,131 @@
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, on, {
-    auto duration = 60s;
-    auto callback = []() {};
+BENCHMARK_WRAPPER(SlowVibratorBench, on, {
+    auto duration = MAX_ON_DURATION_MS;
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<void>(onFn, "on", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, off, {
-    auto duration = 60s;
-    auto callback = []() {};
+BENCHMARK_WRAPPER(SlowVibratorBench, off, {
+    auto duration = MAX_ON_DURATION_MS;
 
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
-        if (!checkHalResult(ret, state)) {
-            continue;
+        auto cb = mCallbacks.next();
+        auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); };
+        if (shouldSkipWithError<void>(onFn, "on", state)) {
+            return;
         }
+        auto offFn = [&](auto hal) { return hal->off(); };
         state.ResumeTiming();
-        turnVibratorOff(state);
+
+        // Test
+        if (shouldSkipWithError<void>(offFn, "off", state)) {
+            return;
+        }
+
+        // Cleanup
+        state.PauseTiming();
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, setAmplitude, {
-    if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
         return;
     }
 
-    auto duration = 60s;
-    auto callback = []() {};
+    auto duration = MAX_ON_DURATION_MS;
     auto amplitude = 1.0f;
+    auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); };
 
-    for (auto _ : state) {
-        state.PauseTiming();
-        vibrator::HalController controller;
-        controller.init();
-        auto result =
-                halCall<void>(controller, [&](auto hal) { return hal->on(duration, callback); });
-        if (!checkHalResult(result, state)) {
-            continue;
-        }
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(controller, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
-        }
-    }
-});
-
-BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, {
-    if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
+    auto onFn = [&](auto hal) { return hal->on(duration, [&]() {}); };
+    if (shouldSkipWithError<void>(onFn, "on", state)) {
         return;
     }
 
-    auto duration = 60s;
-    auto callback = []() {};
-    auto amplitude = 1.0f;
-
-    auto onResult =
-            halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
-    checkHalResult(onResult, state);
-
     for (auto _ : state) {
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        checkHalResult(ret, state);
+        if (shouldSkipWithError<void>(setAmplitudeFn, "setAmplitude", state)) {
+            return;
+        }
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, setExternalControl, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
         return;
     }
 
+    auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
+
     for (auto _ : state) {
-        state.PauseTiming();
-        vibrator::HalController controller;
-        controller.init();
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(controller, [](auto hal) { return hal->setExternalControl(true); });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            auto result = halCall<void>(controller,
-                                        [](auto hal) { return hal->setExternalControl(false); });
-            checkHalResult(result, state);
+        // Test
+        if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(disableExternalControl(), state)) {
+            return;
+        }
+        state.ResumeTiming();
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, setExternalControlCached, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
-        return;
-    }
-
-    for (auto _ : state) {
-        state.ResumeTiming();
-        auto result =
-                halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); });
-        state.PauseTiming();
-        if (checkHalResult(result, state)) {
-            auto ret = halCall<void>(mController,
-                                     [](auto hal) { return hal->setExternalControl(false); });
-            checkHalResult(ret, state);
-        }
-    }
-});
-
-BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitudeCached, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL, state)) {
+BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitude, {
+    auto externalAmplitudeControl = vibrator::Capabilities::EXTERNAL_CONTROL &
+            vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL;
+    if (shouldSkipWithMissingCapabilityMessage(externalAmplitudeControl, state)) {
         return;
     }
 
     auto amplitude = 1.0f;
+    auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); };
+    auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
 
-    auto onResult =
-            halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); });
-    checkHalResult(onResult, state);
-
-    for (auto _ : state) {
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        checkHalResult(ret, state);
+    if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
+        return;
     }
 
-    auto offResult =
-            halCall<void>(mController, [](auto hal) { return hal->setExternalControl(false); });
-    checkHalResult(offResult, state);
+    for (auto _ : state) {
+        if (shouldSkipWithError<void>(setAmplitudeFn, "setExternalAmplitude", state)) {
+            return;
+        }
+    }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, getInfo, {
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
         vibrator::HalController controller;
         controller.init();
         state.ResumeTiming();
-        auto result = controller.getInfo();
-        checkHalResult(result.capabilities, state);
-        checkHalResult(result.supportedEffects, state);
-        checkHalResult(result.supportedPrimitives, state);
-        checkHalResult(result.primitiveDurations, state);
-        checkHalResult(result.resonantFrequency, state);
-        checkHalResult(result.qFactor, state);
+
+        controller.getInfo();
     }
 });
 
@@ -280,13 +367,7 @@
     mController.getInfo();
 
     for (auto _ : state) {
-        auto result = mController.getInfo();
-        checkHalResult(result.capabilities, state);
-        checkHalResult(result.supportedEffects, state);
-        checkHalResult(result.supportedPrimitives, state);
-        checkHalResult(result.primitiveDurations, state);
-        checkHalResult(result.resonantFrequency, state);
-        checkHalResult(result.qFactor, state);
+        mController.getInfo();
     }
 });
 
@@ -329,82 +410,114 @@
     }
 };
 
+class SlowVibratorEffectsBench : public VibratorEffectsBench {
+public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
+    }
+};
+
 BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnEnable, {
-    if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
         return;
     }
     if (!hasArgs(state)) {
+        state.SkipWithMessage("missing args");
         return;
     }
 
     int32_t id = 1;
     auto effect = getEffect(state);
     auto strength = getStrength(state);
+    auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); };
+    auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); };
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<void>(mController, [&](auto hal) {
-            return hal->alwaysOnEnable(id, effect, strength);
-        });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            auto disableResult =
-                    halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); });
-            checkHalResult(disableResult, state);
+        // Test
+        if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) {
+            return;
+        }
+        state.ResumeTiming();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnDisable, {
-    if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
         return;
     }
     if (!hasArgs(state)) {
+        state.SkipWithMessage("missing args");
         return;
     }
 
     int32_t id = 1;
     auto effect = getEffect(state);
     auto strength = getStrength(state);
+    auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); };
+    auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); };
 
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
-        auto enableResult = halCall<void>(mController, [&](auto hal) {
-            return hal->alwaysOnEnable(id, effect, strength);
-        });
-        if (!checkHalResult(enableResult, state)) {
-            continue;
+        if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
+            return;
         }
         state.ResumeTiming();
-        auto disableResult =
-                halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); });
-        checkHalResult(disableResult, state);
+
+        // Test
+        if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) {
+            return;
+        }
     }
 });
 
-BENCHMARK_WRAPPER(VibratorEffectsBench, performEffect, {
+BENCHMARK_WRAPPER(SlowVibratorEffectsBench, performEffect, {
     if (!hasArgs(state)) {
+        state.SkipWithMessage("missing args");
         return;
     }
 
     auto effect = getEffect(state);
     auto strength = getStrength(state);
-    auto callback = []() {};
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) {
-            return hal->performEffect(effect, strength, callback);
-        });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto performFn = [&](auto hal) {
+            return hal->performEffect(effect, strength, cb.completeFn());
+        };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<milliseconds>(performFn, "performEffect", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
-class VibratorPrimitivesBench : public VibratorBench {
+class SlowVibratorPrimitivesBench : public VibratorBench {
 public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
+    }
+
     static void DefaultArgs(Benchmark* b) {
         vibrator::HalController controller;
         auto primitivesResult = controller.getInfo().supportedPrimitives;
@@ -439,11 +552,12 @@
     }
 };
 
-BENCHMARK_WRAPPER(VibratorPrimitivesBench, performComposedEffect, {
-    if (!hasCapabilities(vibrator::Capabilities::COMPOSE_EFFECTS, state)) {
+BENCHMARK_WRAPPER(SlowVibratorPrimitivesBench, performComposedEffect, {
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::COMPOSE_EFFECTS, state)) {
         return;
     }
     if (!hasArgs(state)) {
+        state.SkipWithMessage("missing args");
         return;
     }
 
@@ -452,20 +566,30 @@
     effect.scale = 1.0f;
     effect.delayMs = static_cast<int32_t>(0);
 
-    std::vector<CompositeEffect> effects;
-    effects.push_back(effect);
-    auto callback = []() {};
+    std::vector<CompositeEffect> effects = {effect};
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) {
-            return hal->performComposedEffect(effects, callback);
-        });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto performFn = [&](auto hal) {
+            return hal->performComposedEffect(effects, cb.completeFn());
+        };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<milliseconds>(performFn, "performComposedEffect", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
-BENCHMARK_MAIN();
\ No newline at end of file
+BENCHMARK_MAIN();
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index 6b73d17..f97442d 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -64,7 +64,29 @@
      */
     Info getInfo() {
         static Info sDefaultInfo = InfoCache().get();
-        return apply<Info>([](HalWrapper* hal) { return hal->getInfo(); }, sDefaultInfo, "getInfo");
+        if (!init()) {
+            ALOGV("Skipped getInfo because Vibrator HAL is not available");
+            return sDefaultInfo;
+        }
+        std::shared_ptr<HalWrapper> hal;
+        {
+            std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+            hal = mConnectedHal;
+        }
+
+        for (int i = 0; i < MAX_RETRIES; i++) {
+            Info result = hal.get()->getInfo();
+            result.logFailures();
+            if (result.shouldRetry()) {
+                tryReconnect();
+            } else {
+                return result;
+            }
+        }
+
+        Info result = hal.get()->getInfo();
+        result.logFailures();
+        return result;
     }
 
     /* Calls given HAL function, applying automatic retries to reconnect with the HAL when the
@@ -72,7 +94,7 @@
      */
     template <typename T>
     HalResult<T> doWithRetry(const HalFunction<HalResult<T>>& halFn, const char* functionName) {
-        return apply(halFn, HalResult<T>::unsupported(), functionName);
+        return doWithRetry<T>(halFn, HalResult<T>::unsupported(), functionName);
     }
 
 private:
@@ -90,7 +112,8 @@
      * function name is for logging purposes.
      */
     template <typename T>
-    T apply(const HalFunction<T>& halFn, T defaultValue, const char* functionName) {
+    HalResult<T> doWithRetry(const HalFunction<HalResult<T>>& halFn, HalResult<T> defaultValue,
+                             const char* functionName) {
         if (!init()) {
             ALOGV("Skipped %s because Vibrator HAL is not available", functionName);
             return defaultValue;
@@ -101,16 +124,22 @@
             hal = mConnectedHal;
         }
 
-        for (int i = 0; i < MAX_RETRIES; i++) {
-            T result = halFn(hal.get());
-            if (result.isFailedLogged(functionName)) {
-                tryReconnect();
-            } else {
-                return result;
-            }
+        HalResult<T> result = doOnce(hal.get(), halFn, functionName);
+        for (int i = 0; i < MAX_RETRIES && result.shouldRetry(); i++) {
+            tryReconnect();
+            result = doOnce(hal.get(), halFn, functionName);
         }
+        return result;
+    }
 
-        return halFn(hal.get());
+    template <typename T>
+    HalResult<T> doOnce(HalWrapper* hal, const HalFunction<HalResult<T>>& halFn,
+                        const char* functionName) {
+        HalResult<T> result = halFn(hal);
+        if (result.isFailed()) {
+            ALOGE("Vibrator HAL %s failed: %s", functionName, result.errorMessage());
+        }
+        return result;
     }
 };
 
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index d2cc9ad..39c4eb4 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -31,101 +31,154 @@
 
 // -------------------------------------------------------------------------------------------------
 
+// Base class to represent a generic result of a call to the Vibrator HAL wrapper.
+class BaseHalResult {
+public:
+    bool isOk() const { return mStatus == SUCCESS; }
+    bool isFailed() const { return mStatus == FAILED; }
+    bool isUnsupported() const { return mStatus == UNSUPPORTED; }
+    bool shouldRetry() const { return isFailed() && mDeadObject; }
+    const char* errorMessage() const { return mErrorMessage.c_str(); }
+
+protected:
+    enum Status { SUCCESS, UNSUPPORTED, FAILED };
+    Status mStatus;
+    std::string mErrorMessage;
+    bool mDeadObject;
+
+    explicit BaseHalResult(Status status, const char* errorMessage = "", bool deadObject = false)
+          : mStatus(status), mErrorMessage(errorMessage), mDeadObject(deadObject) {}
+    virtual ~BaseHalResult() = default;
+};
+
 // Result of a call to the Vibrator HAL wrapper, holding data if successful.
 template <typename T>
-class HalResult {
+class HalResult : public BaseHalResult {
 public:
     static HalResult<T> ok(T value) { return HalResult(value); }
-    static HalResult<T> failed(std::string msg) {
-        return HalResult(std::move(msg), /* unsupported= */ false);
+    static HalResult<T> unsupported() { return HalResult(Status::UNSUPPORTED); }
+    static HalResult<T> failed(const char* msg) { return HalResult(Status::FAILED, msg); }
+    static HalResult<T> transactionFailed(const char* msg) {
+        return HalResult(Status::FAILED, msg, /* deadObject= */ true);
     }
-    static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
 
+    // This will throw std::bad_optional_access if this result is not ok.
+    const T& value() const { return mValue.value(); }
+    const T valueOr(T&& defaultValue) const { return mValue.value_or(defaultValue); }
+
+private:
+    std::optional<T> mValue;
+
+    explicit HalResult(T value)
+          : BaseHalResult(Status::SUCCESS), mValue(std::make_optional(value)) {}
+    explicit HalResult(Status status, const char* errorMessage = "", bool deadObject = false)
+          : BaseHalResult(status, errorMessage, deadObject), mValue() {}
+};
+
+// Empty result of a call to the Vibrator HAL wrapper.
+template <>
+class HalResult<void> : public BaseHalResult {
+public:
+    static HalResult<void> ok() { return HalResult(Status::SUCCESS); }
+    static HalResult<void> unsupported() { return HalResult(Status::UNSUPPORTED); }
+    static HalResult<void> failed(const char* msg) { return HalResult(Status::FAILED, msg); }
+    static HalResult<void> transactionFailed(const char* msg) {
+        return HalResult(Status::FAILED, msg, /* deadObject= */ true);
+    }
+
+private:
+    explicit HalResult(Status status, const char* errorMessage = "", bool deadObject = false)
+          : BaseHalResult(status, errorMessage, deadObject) {}
+};
+
+// -------------------------------------------------------------------------------------------------
+
+// Factory functions that convert failed HIDL/AIDL results into HalResult instances.
+// Implementation of static template functions needs to be in this header file for the linker.
+class HalResultFactory {
+public:
+    template <typename T>
     static HalResult<T> fromStatus(binder::Status status, T data) {
+        return status.isOk() ? HalResult<T>::ok(data) : fromFailedStatus<T>(status);
+    }
+
+    template <typename T>
+    static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status status, T data) {
+        return (status == hardware::vibrator::V1_0::Status::OK) ? HalResult<T>::ok(data)
+                                                                : fromFailedStatus<T>(status);
+    }
+
+    template <typename T, typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T data) {
+        return ret.isOk() ? HalResult<T>::ok(data) : fromFailedReturn<T, R>(ret);
+    }
+
+    template <typename T, typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret,
+                                   hardware::vibrator::V1_0::Status status, T data) {
+        return ret.isOk() ? fromStatus<T>(status, data) : fromFailedReturn<T, R>(ret);
+    }
+
+    static HalResult<void> fromStatus(status_t status) {
+        return (status == android::OK) ? HalResult<void>::ok() : fromFailedStatus<void>(status);
+    }
+
+    static HalResult<void> fromStatus(binder::Status status) {
+        return status.isOk() ? HalResult<void>::ok() : fromFailedStatus<void>(status);
+    }
+
+    static HalResult<void> fromStatus(hardware::vibrator::V1_0::Status status) {
+        return (status == hardware::vibrator::V1_0::Status::OK) ? HalResult<void>::ok()
+                                                                : fromFailedStatus<void>(status);
+    }
+
+    template <typename R>
+    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
+        return ret.isOk() ? HalResult<void>::ok() : fromFailedReturn<void, R>(ret);
+    }
+
+private:
+    template <typename T>
+    static HalResult<T> fromFailedStatus(status_t status) {
+        auto msg = "status_t = " + statusToString(status);
+        return (status == android::DEAD_OBJECT) ? HalResult<T>::transactionFailed(msg.c_str())
+                                                : HalResult<T>::failed(msg.c_str());
+    }
+
+    template <typename T>
+    static HalResult<T> fromFailedStatus(binder::Status status) {
         if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
             status.transactionError() == android::UNKNOWN_TRANSACTION) {
             // UNKNOWN_TRANSACTION means the HAL implementation is an older version, so this is
             // the same as the operation being unsupported by this HAL. Should not retry.
             return HalResult<T>::unsupported();
         }
-        if (status.isOk()) {
-            return HalResult<T>::ok(data);
+        if (status.exceptionCode() == binder::Status::EX_TRANSACTION_FAILED) {
+            return HalResult<T>::transactionFailed(status.toString8().c_str());
         }
         return HalResult<T>::failed(status.toString8().c_str());
     }
-    static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status status, T data);
 
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T data);
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret,
-                                   hardware::vibrator::V1_0::Status status, T data);
-
-    // This will throw std::bad_optional_access if this result is not ok.
-    const T& value() const { return mValue.value(); }
-    const T valueOr(T&& defaultValue) const { return mValue.value_or(defaultValue); }
-    bool isOk() const { return !mUnsupported && mValue.has_value(); }
-    bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
-    bool isUnsupported() const { return mUnsupported; }
-    const char* errorMessage() const { return mErrorMessage.c_str(); }
-    bool isFailedLogged(const char* functionNameForLogging) const {
-        if (isFailed()) {
-            ALOGE("Vibrator HAL %s failed: %s", functionNameForLogging, errorMessage());
-            return true;
+    template <typename T>
+    static HalResult<T> fromFailedStatus(hardware::vibrator::V1_0::Status status) {
+        switch (status) {
+            case hardware::vibrator::V1_0::Status::UNSUPPORTED_OPERATION:
+                return HalResult<T>::unsupported();
+            default:
+                auto msg = "android::hardware::vibrator::V1_0::Status = " + toString(status);
+                return HalResult<T>::failed(msg.c_str());
         }
-        return false;
     }
 
-private:
-    std::optional<T> mValue;
-    std::string mErrorMessage;
-    bool mUnsupported;
-
-    explicit HalResult(T value)
-          : mValue(std::make_optional(value)), mErrorMessage(), mUnsupported(false) {}
-    explicit HalResult(std::string errorMessage, bool unsupported)
-          : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {}
-};
-
-// Empty result of a call to the Vibrator HAL wrapper.
-template <>
-class HalResult<void> {
-public:
-    static HalResult<void> ok() { return HalResult(); }
-    static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
-    static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
-
-    static HalResult<void> fromStatus(status_t status);
-    static HalResult<void> fromStatus(binder::Status status);
-    static HalResult<void> fromStatus(hardware::vibrator::V1_0::Status status);
-
-    template <typename R>
-    static HalResult<void> fromReturn(hardware::Return<R>& ret);
-
-    bool isOk() const { return !mUnsupported && !mFailed; }
-    bool isFailed() const { return !mUnsupported && mFailed; }
-    bool isUnsupported() const { return mUnsupported; }
-    const char* errorMessage() const { return mErrorMessage.c_str(); }
-    bool isFailedLogged(const char* functionNameForLogging) const {
-        if (isFailed()) {
-            ALOGE("Vibrator HAL %s failed: %s", functionNameForLogging, errorMessage());
-            return true;
-        }
-        return false;
+    template <typename T, typename R>
+    static HalResult<T> fromFailedReturn(hardware::Return<R>& ret) {
+        return ret.isDeadObject() ? HalResult<T>::transactionFailed(ret.description().c_str())
+                                  : HalResult<T>::failed(ret.description().c_str());
     }
-
-private:
-    std::string mErrorMessage;
-    bool mFailed;
-    bool mUnsupported;
-
-    explicit HalResult(bool unsupported = false)
-          : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {}
-    explicit HalResult(std::string errorMessage)
-          : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
 };
 
+// -------------------------------------------------------------------------------------------------
+
 class HalCallbackWrapper : public hardware::vibrator::BnVibratorCallback {
 public:
     HalCallbackWrapper(std::function<void()> completionCallback)
@@ -192,21 +245,44 @@
     const HalResult<float> qFactor;
     const HalResult<std::vector<float>> maxAmplitudes;
 
-    bool isFailedLogged(const char*) const {
-        return capabilities.isFailedLogged("getCapabilities") ||
-                supportedEffects.isFailedLogged("getSupportedEffects") ||
-                supportedBraking.isFailedLogged("getSupportedBraking") ||
-                supportedPrimitives.isFailedLogged("getSupportedPrimitives") ||
-                primitiveDurations.isFailedLogged("getPrimitiveDuration") ||
-                primitiveDelayMax.isFailedLogged("getPrimitiveDelayMax") ||
-                pwlePrimitiveDurationMax.isFailedLogged("getPwlePrimitiveDurationMax") ||
-                compositionSizeMax.isFailedLogged("getCompositionSizeMax") ||
-                pwleSizeMax.isFailedLogged("getPwleSizeMax") ||
-                minFrequency.isFailedLogged("getMinFrequency") ||
-                resonantFrequency.isFailedLogged("getResonantFrequency") ||
-                frequencyResolution.isFailedLogged("getFrequencyResolution") ||
-                qFactor.isFailedLogged("getQFactor") ||
-                maxAmplitudes.isFailedLogged("getMaxAmplitudes");
+    void logFailures() const {
+        logFailure<Capabilities>(capabilities, "getCapabilities");
+        logFailure<std::vector<hardware::vibrator::Effect>>(supportedEffects,
+                                                            "getSupportedEffects");
+        logFailure<std::vector<hardware::vibrator::Braking>>(supportedBraking,
+                                                             "getSupportedBraking");
+        logFailure<std::vector<hardware::vibrator::CompositePrimitive>>(supportedPrimitives,
+                                                                        "getSupportedPrimitives");
+        logFailure<std::vector<std::chrono::milliseconds>>(primitiveDurations,
+                                                           "getPrimitiveDuration");
+        logFailure<std::chrono::milliseconds>(primitiveDelayMax, "getPrimitiveDelayMax");
+        logFailure<std::chrono::milliseconds>(pwlePrimitiveDurationMax,
+                                              "getPwlePrimitiveDurationMax");
+        logFailure<int32_t>(compositionSizeMax, "getCompositionSizeMax");
+        logFailure<int32_t>(pwleSizeMax, "getPwleSizeMax");
+        logFailure<float>(minFrequency, "getMinFrequency");
+        logFailure<float>(resonantFrequency, "getResonantFrequency");
+        logFailure<float>(frequencyResolution, "getFrequencyResolution");
+        logFailure<float>(qFactor, "getQFactor");
+        logFailure<std::vector<float>>(maxAmplitudes, "getMaxAmplitudes");
+    }
+
+    bool shouldRetry() const {
+        return capabilities.shouldRetry() || supportedEffects.shouldRetry() ||
+                supportedBraking.shouldRetry() || supportedPrimitives.shouldRetry() ||
+                primitiveDurations.shouldRetry() || primitiveDelayMax.shouldRetry() ||
+                pwlePrimitiveDurationMax.shouldRetry() || compositionSizeMax.shouldRetry() ||
+                pwleSizeMax.shouldRetry() || minFrequency.shouldRetry() ||
+                resonantFrequency.shouldRetry() || frequencyResolution.shouldRetry() ||
+                qFactor.shouldRetry() || maxAmplitudes.shouldRetry();
+    }
+
+private:
+    template <typename T>
+    void logFailure(HalResult<T> result, const char* functionName) const {
+        if (result.isFailed()) {
+            ALOGE("Vibrator HAL %s failed: %s", functionName, result.errorMessage());
+        }
     }
 };
 
@@ -230,27 +306,29 @@
     }
 
 private:
+    // Create a transaction failed results as default so we can retry on the first time we get them.
     static const constexpr char* MSG = "never loaded";
-    HalResult<Capabilities> mCapabilities = HalResult<Capabilities>::failed(MSG);
+    HalResult<Capabilities> mCapabilities = HalResult<Capabilities>::transactionFailed(MSG);
     HalResult<std::vector<hardware::vibrator::Effect>> mSupportedEffects =
-            HalResult<std::vector<hardware::vibrator::Effect>>::failed(MSG);
+            HalResult<std::vector<hardware::vibrator::Effect>>::transactionFailed(MSG);
     HalResult<std::vector<hardware::vibrator::Braking>> mSupportedBraking =
-            HalResult<std::vector<hardware::vibrator::Braking>>::failed(MSG);
+            HalResult<std::vector<hardware::vibrator::Braking>>::transactionFailed(MSG);
     HalResult<std::vector<hardware::vibrator::CompositePrimitive>> mSupportedPrimitives =
-            HalResult<std::vector<hardware::vibrator::CompositePrimitive>>::failed(MSG);
+            HalResult<std::vector<hardware::vibrator::CompositePrimitive>>::transactionFailed(MSG);
     HalResult<std::vector<std::chrono::milliseconds>> mPrimitiveDurations =
-            HalResult<std::vector<std::chrono::milliseconds>>::failed(MSG);
+            HalResult<std::vector<std::chrono::milliseconds>>::transactionFailed(MSG);
     HalResult<std::chrono::milliseconds> mPrimitiveDelayMax =
-            HalResult<std::chrono::milliseconds>::failed(MSG);
+            HalResult<std::chrono::milliseconds>::transactionFailed(MSG);
     HalResult<std::chrono::milliseconds> mPwlePrimitiveDurationMax =
-            HalResult<std::chrono::milliseconds>::failed(MSG);
-    HalResult<int32_t> mCompositionSizeMax = HalResult<int>::failed(MSG);
-    HalResult<int32_t> mPwleSizeMax = HalResult<int>::failed(MSG);
-    HalResult<float> mMinFrequency = HalResult<float>::failed(MSG);
-    HalResult<float> mResonantFrequency = HalResult<float>::failed(MSG);
-    HalResult<float> mFrequencyResolution = HalResult<float>::failed(MSG);
-    HalResult<float> mQFactor = HalResult<float>::failed(MSG);
-    HalResult<std::vector<float>> mMaxAmplitudes = HalResult<std::vector<float>>::failed(MSG);
+            HalResult<std::chrono::milliseconds>::transactionFailed(MSG);
+    HalResult<int32_t> mCompositionSizeMax = HalResult<int>::transactionFailed(MSG);
+    HalResult<int32_t> mPwleSizeMax = HalResult<int>::transactionFailed(MSG);
+    HalResult<float> mMinFrequency = HalResult<float>::transactionFailed(MSG);
+    HalResult<float> mResonantFrequency = HalResult<float>::transactionFailed(MSG);
+    HalResult<float> mFrequencyResolution = HalResult<float>::transactionFailed(MSG);
+    HalResult<float> mQFactor = HalResult<float>::transactionFailed(MSG);
+    HalResult<std::vector<float>> mMaxAmplitudes =
+            HalResult<std::vector<float>>::transactionFailed(MSG);
 
     friend class HalWrapper;
 };
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 8e77bc5..15fde91 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -107,17 +107,38 @@
     ASSERT_EQ(1, mConnectCounter);
 }
 
-TEST_F(VibratorHalControllerTest, TestGetInfoRetriesOnAnyFailure) {
+TEST_F(VibratorHalControllerTest, TestGetInfoRetriesOnTransactionFailure) {
     EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
     EXPECT_CALL(*mMockHal.get(), getCapabilitiesInternal())
             .Times(Exactly(2))
-            .WillOnce(Return(vibrator::HalResult<vibrator::Capabilities>::failed("message")))
+            .WillOnce(Return(vibrator::HalResult<vibrator::Capabilities>::transactionFailed("msg")))
             .WillRepeatedly(Return(vibrator::HalResult<vibrator::Capabilities>::ok(
                     vibrator::Capabilities::ON_CALLBACK)));
 
     auto result = mController->getInfo();
-    ASSERT_FALSE(result.capabilities.isFailed());
+    ASSERT_TRUE(result.capabilities.isOk());
+    ASSERT_EQ(1, mConnectCounter);
+}
 
+TEST_F(VibratorHalControllerTest, TestGetInfoDoesNotRetryOnOperationFailure) {
+    EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(0));
+    EXPECT_CALL(*mMockHal.get(), getCapabilitiesInternal())
+            .Times(Exactly(1))
+            .WillRepeatedly(Return(vibrator::HalResult<vibrator::Capabilities>::failed("msg")));
+
+    auto result = mController->getInfo();
+    ASSERT_TRUE(result.capabilities.isFailed());
+    ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorHalControllerTest, TestGetInfoDoesNotRetryOnUnsupported) {
+    EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(0));
+    EXPECT_CALL(*mMockHal.get(), getCapabilitiesInternal())
+            .Times(Exactly(1))
+            .WillRepeatedly(Return(vibrator::HalResult<vibrator::Capabilities>::unsupported()));
+
+    auto result = mController->getInfo();
+    ASSERT_TRUE(result.capabilities.isUnsupported());
     ASSERT_EQ(1, mConnectCounter);
 }
 
@@ -128,48 +149,54 @@
 
     auto result = mController->doWithRetry<void>(ON_FN, "on");
     ASSERT_TRUE(result.isOk());
-
     ASSERT_EQ(1, mConnectCounter);
 }
 
-TEST_F(VibratorHalControllerTest, TestUnsupportedApiResultDoNotResetHalConnection) {
+TEST_F(VibratorHalControllerTest, TestUnsupportedApiResultDoesNotResetHalConnection) {
+    EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(0));
     EXPECT_CALL(*mMockHal.get(), off())
             .Times(Exactly(1))
             .WillRepeatedly(Return(vibrator::HalResult<void>::unsupported()));
 
-    ASSERT_EQ(0, mConnectCounter);
     auto result = mController->doWithRetry<void>(OFF_FN, "off");
     ASSERT_TRUE(result.isUnsupported());
     ASSERT_EQ(1, mConnectCounter);
 }
 
-TEST_F(VibratorHalControllerTest, TestFailedApiResultResetsHalConnection) {
+TEST_F(VibratorHalControllerTest, TestOperationFailedApiResultDoesNotResetHalConnection) {
+    EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(0));
     EXPECT_CALL(*mMockHal.get(), on(_, _))
-            .Times(Exactly(2))
+            .Times(Exactly(1))
             .WillRepeatedly(Return(vibrator::HalResult<void>::failed("message")));
-    EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
-
-    ASSERT_EQ(0, mConnectCounter);
 
     auto result = mController->doWithRetry<void>(ON_FN, "on");
     ASSERT_TRUE(result.isFailed());
     ASSERT_EQ(1, mConnectCounter);
 }
 
-TEST_F(VibratorHalControllerTest, TestFailedApiResultReturnsSuccessAfterRetries) {
+TEST_F(VibratorHalControllerTest, TestTransactionFailedApiResultResetsHalConnection) {
+    EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
+    EXPECT_CALL(*mMockHal.get(), on(_, _))
+            .Times(Exactly(2))
+            .WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
+
+    auto result = mController->doWithRetry<void>(ON_FN, "on");
+    ASSERT_TRUE(result.isFailed());
+    ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorHalControllerTest, TestTransactionFailedApiResultReturnsSuccessAfterRetries) {
     {
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), ping())
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(vibrator::HalResult<void>::failed("message")));
+                .WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
         EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
         EXPECT_CALL(*mMockHal.get(), ping())
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(vibrator::HalResult<void>::ok()));
     }
 
-    ASSERT_EQ(0, mConnectCounter);
-
     auto result = mController->doWithRetry<void>(PING_FN, "ping");
     ASSERT_TRUE(result.isOk());
     ASSERT_EQ(1, mConnectCounter);
@@ -221,23 +248,24 @@
                 });
         EXPECT_CALL(*mMockHal.get(), ping())
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(vibrator::HalResult<void>::failed("message")));
+                .WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
         EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
         EXPECT_CALL(*mMockHal.get(), ping())
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(vibrator::HalResult<void>::failed("message")));
+                .WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
     }
 
-    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
-    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+    auto counter = vibrator::TestCounter(0);
 
-    auto onFn = [&](vibrator::HalWrapper* hal) { return hal->on(10ms, callback); };
+    auto onFn = [&](vibrator::HalWrapper* hal) {
+        return hal->on(10ms, [&counter] { counter.increment(); });
+    };
     ASSERT_TRUE(mController->doWithRetry<void>(onFn, "on").isOk());
     ASSERT_TRUE(mController->doWithRetry<void>(PING_FN, "ping").isFailed());
     mMockHal.reset();
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(0, counter.get());
 
     // Callback triggered even after HalWrapper was reconnected.
-    std::this_thread::sleep_for(15ms);
-    ASSERT_EQ(1, *callbackCounter.get());
+    counter.tryWaitUntilCountIsAtLeast(1, 500ms);
+    ASSERT_EQ(1, counter.get());
 }
diff --git a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
index e5fbbae..11a8b66 100644
--- a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
@@ -98,8 +98,10 @@
                 .WillRepeatedly(Return(voidResult));
 
         if (cardinality > 1) {
-            // One reconnection call after each failure.
-            EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(7 * cardinality));
+            // One reconnection for each retry.
+            EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(7 * (cardinality - 1)));
+        } else {
+            EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(0));
         }
     }
 };
@@ -141,14 +143,12 @@
     ASSERT_EQ(1, mConnectCounter);
 }
 
-TEST_F(VibratorManagerHalControllerTest, TestUnsupportedApiResultDoNotResetHalConnection) {
+TEST_F(VibratorManagerHalControllerTest, TestUnsupportedApiResultDoesNotResetHalConnection) {
     setHalExpectations(/* cardinality= */ 1, vibrator::HalResult<void>::unsupported(),
                        vibrator::HalResult<vibrator::ManagerCapabilities>::unsupported(),
                        vibrator::HalResult<std::vector<int32_t>>::unsupported(),
                        vibrator::HalResult<std::shared_ptr<HalController>>::unsupported());
 
-    ASSERT_EQ(0, mConnectCounter);
-
     ASSERT_TRUE(mController->ping().isUnsupported());
     ASSERT_TRUE(mController->getCapabilities().isUnsupported());
     ASSERT_TRUE(mController->getVibratorIds().isUnsupported());
@@ -160,13 +160,28 @@
     ASSERT_EQ(1, mConnectCounter);
 }
 
-TEST_F(VibratorManagerHalControllerTest, TestFailedApiResultResetsHalConnection) {
-    setHalExpectations(MAX_ATTEMPTS, vibrator::HalResult<void>::failed("message"),
-                       vibrator::HalResult<vibrator::ManagerCapabilities>::failed("message"),
-                       vibrator::HalResult<std::vector<int32_t>>::failed("message"),
-                       vibrator::HalResult<std::shared_ptr<HalController>>::failed("message"));
+TEST_F(VibratorManagerHalControllerTest, TestOperationFailedApiResultDoesNotResetHalConnection) {
+    setHalExpectations(/* cardinality= */ 1, vibrator::HalResult<void>::failed("msg"),
+                       vibrator::HalResult<vibrator::ManagerCapabilities>::failed("msg"),
+                       vibrator::HalResult<std::vector<int32_t>>::failed("msg"),
+                       vibrator::HalResult<std::shared_ptr<HalController>>::failed("msg"));
 
-    ASSERT_EQ(0, mConnectCounter);
+    ASSERT_TRUE(mController->ping().isFailed());
+    ASSERT_TRUE(mController->getCapabilities().isFailed());
+    ASSERT_TRUE(mController->getVibratorIds().isFailed());
+    ASSERT_TRUE(mController->getVibrator(VIBRATOR_ID).isFailed());
+    ASSERT_TRUE(mController->prepareSynced(VIBRATOR_IDS).isFailed());
+    ASSERT_TRUE(mController->triggerSynced([]() {}).isFailed());
+    ASSERT_TRUE(mController->cancelSynced().isFailed());
+
+    ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestTransactionFailedApiResultResetsHalConnection) {
+    setHalExpectations(MAX_ATTEMPTS, vibrator::HalResult<void>::transactionFailed("m"),
+                       vibrator::HalResult<vibrator::ManagerCapabilities>::transactionFailed("m"),
+                       vibrator::HalResult<std::vector<int32_t>>::transactionFailed("m"),
+                       vibrator::HalResult<std::shared_ptr<HalController>>::transactionFailed("m"));
 
     ASSERT_TRUE(mController->ping().isFailed());
     ASSERT_TRUE(mController->getCapabilities().isFailed());
@@ -184,14 +199,13 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), ping())
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(vibrator::HalResult<void>::failed("message")));
+                .WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
         EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
         EXPECT_CALL(*mMockHal.get(), ping())
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(vibrator::HalResult<void>::ok()));
     }
 
-    ASSERT_EQ(0, mConnectCounter);
     ASSERT_TRUE(mController->ping().isOk());
     ASSERT_EQ(1, mConnectCounter);
 }
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
index 1593cb1..dffc281 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
@@ -254,13 +254,14 @@
     EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
             .Times(Exactly(3))
             .WillOnce(DoAll(SetArgPointee<1>(nullptr),
-                            Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY))))
+                            Return(Status::fromExceptionCode(
+                                    Status::Exception::EX_TRANSACTION_FAILED))))
             .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
 
     EXPECT_CALL(*mMockVibrator.get(), off())
             .Times(Exactly(3))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_TRANSACTION_FAILED)))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_TRANSACTION_FAILED)))
             .WillRepeatedly(Return(Status()));
 
     // Get vibrator controller is successful even if first getVibrator.
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
index c08cfc6..715c221 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -119,4 +119,4 @@
 
 } // namespace android
 
-#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_
\ No newline at end of file
+#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_
diff --git a/vulkan/Android.bp b/vulkan/Android.bp
index 33599ea..2bf905c 100644
--- a/vulkan/Android.bp
+++ b/vulkan/Android.bp
@@ -42,4 +42,5 @@
     "nulldrv",
     "libvulkan",
     "vkjson",
+    "vkprofiles",
 ]
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 0e45d2d..7ea98f5 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -340,8 +340,9 @@
     ALOGD("Unload builtin Vulkan driver.");
 
     // Close the opened device
-    ALOG_ASSERT(!hal_.dev_->common.close(hal_.dev_->common),
-                "hw_device_t::close() failed.");
+    int err = hal_.dev_->common.close(
+        const_cast<struct hw_device_t*>(&hal_.dev_->common));
+    ALOG_ASSERT(!err, "hw_device_t::close() failed.");
 
     // Close the opened shared library in the hw_module_t
     android_unload_sphal_library(hal_.dev_->common.module->dso);
@@ -1371,6 +1372,11 @@
             android::GraphicsEnv::getInstance().setTargetStats(
                 android::GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION,
                 vulkanApiVersion);
+
+            if (pCreateInfo->pApplicationInfo->pEngineName) {
+                android::GraphicsEnv::getInstance().addVulkanEngineName(
+                    pCreateInfo->pApplicationInfo->pEngineName);
+            }
         }
 
         // Update stats for the extensions requested
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 0df5e77..74d3d9d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "vulkan/vulkan_core.h"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <aidl/android/hardware/graphics/common/Dataspace.h>
@@ -158,6 +159,25 @@
     }
 }
 
+const static VkColorSpaceKHR colorSpaceSupportedByVkEXTSwapchainColorspace[] = {
+    VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT,
+    VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT,
+    VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT,
+    VK_COLOR_SPACE_BT709_LINEAR_EXT,
+    VK_COLOR_SPACE_BT709_NONLINEAR_EXT,
+    VK_COLOR_SPACE_BT2020_LINEAR_EXT,
+    VK_COLOR_SPACE_HDR10_ST2084_EXT,
+    VK_COLOR_SPACE_HDR10_HLG_EXT,
+    VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT,
+    VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT,
+    VK_COLOR_SPACE_PASS_THROUGH_EXT,
+    VK_COLOR_SPACE_DCI_P3_LINEAR_EXT};
+
+const static VkColorSpaceKHR
+    colorSpaceSupportedByVkEXTSwapchainColorspaceOnFP16SurfaceOnly[] = {
+        VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT,
+        VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT};
+
 class TimingInfo {
    public:
     TimingInfo(const VkPresentTimeGOOGLE* qp, uint64_t nativeFrameId)
@@ -535,8 +555,7 @@
     return native_format;
 }
 
-DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace,
-                             PixelFormat pixelFormat) {
+DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace, VkFormat format) {
     switch (colorspace) {
         case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
             return DataSpace::SRGB;
@@ -555,7 +574,7 @@
         case VK_COLOR_SPACE_BT709_NONLINEAR_EXT:
             return DataSpace::SRGB;
         case VK_COLOR_SPACE_BT2020_LINEAR_EXT:
-            if (pixelFormat == PixelFormat::RGBA_FP16) {
+            if (format == VK_FORMAT_R16G16B16A16_SFLOAT) {
                 return DataSpace::BT2020_LINEAR_EXTENDED;
             } else {
                 return DataSpace::BT2020_LINEAR;
@@ -744,71 +763,95 @@
         {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
     };
 
+    VkFormat format = VK_FORMAT_UNDEFINED;
     if (colorspace_ext) {
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_PASS_THROUGH_EXT});
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_BT709_LINEAR_EXT});
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
+        for (VkColorSpaceKHR colorSpace :
+             colorSpaceSupportedByVkEXTSwapchainColorspace) {
+            format = VK_FORMAT_R8G8B8A8_UNORM;
+            if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) {
+                all_formats.emplace_back(
+                    VkSurfaceFormatKHR{format, colorSpace});
+            }
+
+            format = VK_FORMAT_R8G8B8A8_SRGB;
+            if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) {
+                all_formats.emplace_back(
+                    VkSurfaceFormatKHR{format, colorSpace});
+            }
+        }
     }
 
     // NOTE: Any new formats that are added must be coordinated across different
     // Android users.  This includes the ANGLE team (a layered implementation of
     // OpenGL-ES).
 
+    format = VK_FORMAT_R5G6B5_UNORM_PACK16;
     desc.format = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+        all_formats.emplace_back(
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
-            all_formats.emplace_back(
-                VkSurfaceFormatKHR{VK_FORMAT_R5G6B5_UNORM_PACK16,
-                                   VK_COLOR_SPACE_PASS_THROUGH_EXT});
+            for (VkColorSpaceKHR colorSpace :
+                 colorSpaceSupportedByVkEXTSwapchainColorspace) {
+                if (GetNativeDataspace(colorSpace, format) !=
+                    DataSpace::UNKNOWN) {
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
+                }
+            }
         }
     }
 
+    format = VK_FORMAT_R16G16B16A16_SFLOAT;
     desc.format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT;
     if (AHardwareBuffer_isSupported(&desc)) {
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+        all_formats.emplace_back(
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
-            all_formats.emplace_back(
-                VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
-                                   VK_COLOR_SPACE_PASS_THROUGH_EXT});
-            all_formats.emplace_back(
-                VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
-                                   VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT});
-            all_formats.emplace_back(
-                VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
-                                   VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT});
+            for (VkColorSpaceKHR colorSpace :
+                 colorSpaceSupportedByVkEXTSwapchainColorspace) {
+                if (GetNativeDataspace(colorSpace, format) !=
+                    DataSpace::UNKNOWN) {
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
+                }
+            }
+
+            for (
+                VkColorSpaceKHR colorSpace :
+                colorSpaceSupportedByVkEXTSwapchainColorspaceOnFP16SurfaceOnly) {
+                if (GetNativeDataspace(colorSpace, format) !=
+                    DataSpace::UNKNOWN) {
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
+                }
+            }
         }
     }
 
+    format = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
     desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
         all_formats.emplace_back(
-            VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
-                               VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
-            all_formats.emplace_back(
-                VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
-                                   VK_COLOR_SPACE_PASS_THROUGH_EXT});
-            all_formats.emplace_back(
-                VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
-                                   VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
+            for (VkColorSpaceKHR colorSpace :
+                 colorSpaceSupportedByVkEXTSwapchainColorspace) {
+                if (GetNativeDataspace(colorSpace, format) !=
+                    DataSpace::UNKNOWN) {
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
+                }
+            }
         }
     }
 
+    format = VK_FORMAT_R8_UNORM;
     desc.format = AHARDWAREBUFFER_FORMAT_R8_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
         if (colorspace_ext) {
-            all_formats.emplace_back(VkSurfaceFormatKHR{
-                VK_FORMAT_R8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+            all_formats.emplace_back(
+                VkSurfaceFormatKHR{format, VK_COLOR_SPACE_PASS_THROUGH_EXT});
         }
     }
 
@@ -827,18 +870,20 @@
             rgba10x6_formats_ext = true;
         }
     }
+    format = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16;
     desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
     if (AHardwareBuffer_isSupported(&desc) && rgba10x6_formats_ext) {
         all_formats.emplace_back(
-            VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
-                               VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
-            all_formats.emplace_back(
-                VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
-                                   VK_COLOR_SPACE_PASS_THROUGH_EXT});
-            all_formats.emplace_back(
-                VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
-                                   VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
+            for (VkColorSpaceKHR colorSpace :
+                 colorSpaceSupportedByVkEXTSwapchainColorspace) {
+                if (GetNativeDataspace(colorSpace, format) !=
+                    DataSpace::UNKNOWN) {
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
+                }
+            }
         }
     }
 
@@ -1369,197 +1414,221 @@
     const VkPhysicalDevice& pdev = GetData(device).driver_physical_device;
     const InstanceData& instance_data = GetData(pdev);
     const InstanceDriverTable& instance_dispatch = instance_data.driver;
-    if (!instance_dispatch.GetPhysicalDeviceImageFormatProperties2 &&
-            !instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR) {
-        uint64_t native_usage = 0;
-        void* usage_info_pNext = nullptr;
-        VkResult result;
+    if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2 ||
+            instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR) {
+        // Look through the create_info pNext chain passed to createSwapchainKHR
+        // for an image compression control struct.
+        // if one is found AND the appropriate extensions are enabled, create a
+        // VkImageCompressionControlEXT structure to pass on to
+        // GetPhysicalDeviceImageFormatProperties2
+        void* compression_control_pNext = nullptr;
         VkImageCompressionControlEXT image_compression = {};
-        const auto& dispatch = GetData(device).driver;
-        if (dispatch.GetSwapchainGrallocUsage4ANDROID) {
-            ATRACE_BEGIN("GetSwapchainGrallocUsage4ANDROID");
-            VkGrallocUsageInfo2ANDROID gralloc_usage_info = {};
-            gralloc_usage_info.sType =
-                VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID;
-            gralloc_usage_info.format = create_info->imageFormat;
-            gralloc_usage_info.imageUsage = create_info->imageUsage;
-            gralloc_usage_info.swapchainImageUsage = swapchain_image_usage;
-
-            // Look through the pNext chain for an image compression control struct
-            // if one is found AND the appropriate extensions are enabled,
-            // append it to be the gralloc usage pNext chain
-            const VkSwapchainCreateInfoKHR* create_infos = create_info;
-            while (create_infos->pNext) {
-                create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
-                    create_infos->pNext);
-                switch (create_infos->sType) {
-                    case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
-                        const VkImageCompressionControlEXT* compression_infos =
-                            reinterpret_cast<const VkImageCompressionControlEXT*>(
-                                create_infos);
-                        image_compression = *compression_infos;
-                        image_compression.pNext = nullptr;
-                        usage_info_pNext = &image_compression;
-                    } break;
-
-                    default:
-                        // Ignore all other info structs
-                        break;
-                }
+        const VkSwapchainCreateInfoKHR* create_infos = create_info;
+        while (create_infos->pNext) {
+            create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(create_infos->pNext);
+            switch (create_infos->sType) {
+                case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+                    const VkImageCompressionControlEXT* compression_infos =
+                        reinterpret_cast<const VkImageCompressionControlEXT*>(create_infos);
+                    image_compression = *compression_infos;
+                    image_compression.pNext = nullptr;
+                    compression_control_pNext = &image_compression;
+                } break;
+                default:
+                    // Ignore all other info structs
+                    break;
             }
-            gralloc_usage_info.pNext = usage_info_pNext;
-
-            result = dispatch.GetSwapchainGrallocUsage4ANDROID(
-                device, &gralloc_usage_info, &native_usage);
-            ATRACE_END();
-            if (result != VK_SUCCESS) {
-                ALOGE("vkGetSwapchainGrallocUsage4ANDROID failed: %d", result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
-        } else if (dispatch.GetSwapchainGrallocUsage3ANDROID) {
-            ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID");
-            VkGrallocUsageInfoANDROID gralloc_usage_info = {};
-            gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID;
-            gralloc_usage_info.format = create_info->imageFormat;
-            gralloc_usage_info.imageUsage = create_info->imageUsage;
-
-            // Look through the pNext chain for an image compression control struct
-            // if one is found AND the appropriate extensions are enabled,
-            // append it to be the gralloc usage pNext chain
-            const VkSwapchainCreateInfoKHR* create_infos = create_info;
-            while (create_infos->pNext) {
-                create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
-                    create_infos->pNext);
-                switch (create_infos->sType) {
-                    case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
-                        const VkImageCompressionControlEXT* compression_infos =
-                            reinterpret_cast<const VkImageCompressionControlEXT*>(
-                                create_infos);
-                        image_compression = *compression_infos;
-                        image_compression.pNext = nullptr;
-                        usage_info_pNext = &image_compression;
-                    } break;
-
-                    default:
-                        // Ignore all other info structs
-                        break;
-                }
-            }
-            gralloc_usage_info.pNext = usage_info_pNext;
-
-            result = dispatch.GetSwapchainGrallocUsage3ANDROID(
-                device, &gralloc_usage_info, &native_usage);
-            ATRACE_END();
-            if (result != VK_SUCCESS) {
-                ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
-        } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) {
-            uint64_t consumer_usage, producer_usage;
-            ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID");
-            result = dispatch.GetSwapchainGrallocUsage2ANDROID(
-                device, create_info->imageFormat, create_info->imageUsage,
-                swapchain_image_usage, &consumer_usage, &producer_usage);
-            ATRACE_END();
-            if (result != VK_SUCCESS) {
-                ALOGE("vkGetSwapchainGrallocUsage2ANDROID failed: %d", result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
-            native_usage =
-                convertGralloc1ToBufferUsage(producer_usage, consumer_usage);
-        } else if (dispatch.GetSwapchainGrallocUsageANDROID) {
-            ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID");
-            int32_t legacy_usage = 0;
-            result = dispatch.GetSwapchainGrallocUsageANDROID(
-                device, create_info->imageFormat, create_info->imageUsage,
-                &legacy_usage);
-            ATRACE_END();
-            if (result != VK_SUCCESS) {
-                ALOGE("vkGetSwapchainGrallocUsageANDROID failed: %d", result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
-            native_usage = static_cast<uint64_t>(legacy_usage);
         }
-        *producer_usage = native_usage;
 
-        return VK_SUCCESS;
+        // call GetPhysicalDeviceImageFormatProperties2KHR
+        VkPhysicalDeviceExternalImageFormatInfo external_image_format_info = {
+            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
+            .pNext = compression_control_pNext,
+            .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
+        };
+
+        // AHB does not have an sRGB format so we can't pass it to GPDIFP
+        // We need to convert the format to unorm if it is srgb
+        VkFormat format = create_info->imageFormat;
+        if (format == VK_FORMAT_R8G8B8A8_SRGB) {
+            format = VK_FORMAT_R8G8B8A8_UNORM;
+        }
+
+        VkPhysicalDeviceImageFormatInfo2 image_format_info = {
+            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
+            .pNext = &external_image_format_info,
+            .format = format,
+            .type = VK_IMAGE_TYPE_2D,
+            .tiling = VK_IMAGE_TILING_OPTIMAL,
+            .usage = create_info->imageUsage,
+            .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
+        };
+
+        VkAndroidHardwareBufferUsageANDROID ahb_usage;
+        ahb_usage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID;
+        ahb_usage.pNext = nullptr;
+
+        VkImageFormatProperties2 image_format_properties;
+        image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
+        image_format_properties.pNext = &ahb_usage;
+
+        if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) {
+            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2(
+                pdev, &image_format_info, &image_format_properties);
+            if (result != VK_SUCCESS) {
+                ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result);
+                return VK_ERROR_SURFACE_LOST_KHR;
+            }
+        }
+        else {
+            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR(
+                pdev, &image_format_info,
+                &image_format_properties);
+            if (result != VK_SUCCESS) {
+                ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d",
+                    result);
+                return VK_ERROR_SURFACE_LOST_KHR;
+            }
+        }
+
+        // Determine if USAGE_FRONT_BUFFER is needed.
+        // GPDIFP2 has no means of using VkSwapchainImageUsageFlagsANDROID when
+        // querying for producer_usage. So androidHardwareBufferUsage will not
+        // contain USAGE_FRONT_BUFFER. We need to manually check for usage here.
+        if (!(swapchain_image_usage & VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID)) {
+            *producer_usage = ahb_usage.androidHardwareBufferUsage;
+            return VK_SUCCESS;
+        }
+
+        // Check if USAGE_FRONT_BUFFER is supported for this swapchain
+        AHardwareBuffer_Desc ahb_desc = {
+            .width = create_info->imageExtent.width,
+            .height = create_info->imageExtent.height,
+            .layers = create_info->imageArrayLayers,
+            .format = create_info->imageFormat,
+            .usage = ahb_usage.androidHardwareBufferUsage | AHARDWAREBUFFER_USAGE_FRONT_BUFFER,
+            .stride = 0, // stride is always ignored when calling isSupported()
+        };
+
+        // If FRONT_BUFFER is not supported,
+        // then we need to call GetSwapchainGrallocUsageXAndroid below
+        if (AHardwareBuffer_isSupported(&ahb_desc)) {
+            *producer_usage = ahb_usage.androidHardwareBufferUsage;
+            *producer_usage |= AHARDWAREBUFFER_USAGE_FRONT_BUFFER;
+            return VK_SUCCESS;
+        }
     }
 
-    // Look through the create_info pNext chain passed to createSwapchainKHR
-    // for an image compression control struct.
-    // if one is found AND the appropriate extensions are enabled, create a
-    // VkImageCompressionControlEXT structure to pass on to GetPhysicalDeviceImageFormatProperties2
-    void* compression_control_pNext = nullptr;
+    uint64_t native_usage = 0;
+    void* usage_info_pNext = nullptr;
+    VkResult result;
     VkImageCompressionControlEXT image_compression = {};
-    const VkSwapchainCreateInfoKHR* create_infos = create_info;
-    while (create_infos->pNext) {
-        create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(create_infos->pNext);
-        switch (create_infos->sType) {
-            case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
-                const VkImageCompressionControlEXT* compression_infos =
-                    reinterpret_cast<const VkImageCompressionControlEXT*>(create_infos);
-                image_compression = *compression_infos;
-                image_compression.pNext = nullptr;
-                compression_control_pNext = &image_compression;
-            } break;
-            default:
-                // Ignore all other info structs
-                break;
+    const auto& dispatch = GetData(device).driver;
+    if (dispatch.GetSwapchainGrallocUsage4ANDROID) {
+        ATRACE_BEGIN("GetSwapchainGrallocUsage4ANDROID");
+        VkGrallocUsageInfo2ANDROID gralloc_usage_info = {};
+        gralloc_usage_info.sType =
+            VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID;
+        gralloc_usage_info.format = create_info->imageFormat;
+        gralloc_usage_info.imageUsage = create_info->imageUsage;
+        gralloc_usage_info.swapchainImageUsage = swapchain_image_usage;
+
+        // Look through the pNext chain for an image compression control struct
+        // if one is found AND the appropriate extensions are enabled,
+        // append it to be the gralloc usage pNext chain
+        const VkSwapchainCreateInfoKHR* create_infos = create_info;
+        while (create_infos->pNext) {
+            create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
+                create_infos->pNext);
+            switch (create_infos->sType) {
+                case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+                    const VkImageCompressionControlEXT* compression_infos =
+                        reinterpret_cast<const VkImageCompressionControlEXT*>(
+                            create_infos);
+                    image_compression = *compression_infos;
+                    image_compression.pNext = nullptr;
+                    usage_info_pNext = &image_compression;
+                } break;
+
+                default:
+                    // Ignore all other info structs
+                    break;
+            }
         }
-    }
+        gralloc_usage_info.pNext = usage_info_pNext;
 
-    // call GetPhysicalDeviceImageFormatProperties2KHR
-    VkPhysicalDeviceExternalImageFormatInfo external_image_format_info = {
-        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
-        .pNext = compression_control_pNext,
-        .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
-    };
-
-    // AHB does not have an sRGB format so we can't pass it to GPDIFP
-    // We need to convert the format to unorm if it is srgb
-    VkFormat format = create_info->imageFormat;
-    if (format == VK_FORMAT_R8G8B8A8_SRGB) {
-        format = VK_FORMAT_R8G8B8A8_UNORM;
-    }
-
-    VkPhysicalDeviceImageFormatInfo2 image_format_info = {
-        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
-        .pNext = &external_image_format_info,
-        .format = format,
-        .type = VK_IMAGE_TYPE_2D,
-        .tiling = VK_IMAGE_TILING_OPTIMAL,
-        .usage = create_info->imageUsage,
-        .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
-    };
-
-    VkAndroidHardwareBufferUsageANDROID ahb_usage;
-    ahb_usage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID;
-    ahb_usage.pNext = nullptr;
-
-    VkImageFormatProperties2 image_format_properties;
-    image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
-    image_format_properties.pNext = &ahb_usage;
-
-    if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) {
-        VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2(
-            pdev, &image_format_info, &image_format_properties);
+        result = dispatch.GetSwapchainGrallocUsage4ANDROID(
+            device, &gralloc_usage_info, &native_usage);
+        ATRACE_END();
         if (result != VK_SUCCESS) {
-            ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result);
+            ALOGE("vkGetSwapchainGrallocUsage4ANDROID failed: %d", result);
             return VK_ERROR_SURFACE_LOST_KHR;
         }
-    }
-    else {
-        VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR(
-            pdev, &image_format_info,
-            &image_format_properties);
+    } else if (dispatch.GetSwapchainGrallocUsage3ANDROID) {
+        ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID");
+        VkGrallocUsageInfoANDROID gralloc_usage_info = {};
+        gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID;
+        gralloc_usage_info.format = create_info->imageFormat;
+        gralloc_usage_info.imageUsage = create_info->imageUsage;
+
+        // Look through the pNext chain for an image compression control struct
+        // if one is found AND the appropriate extensions are enabled,
+        // append it to be the gralloc usage pNext chain
+        const VkSwapchainCreateInfoKHR* create_infos = create_info;
+        while (create_infos->pNext) {
+            create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
+                create_infos->pNext);
+            switch (create_infos->sType) {
+                case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+                    const VkImageCompressionControlEXT* compression_infos =
+                        reinterpret_cast<const VkImageCompressionControlEXT*>(
+                            create_infos);
+                    image_compression = *compression_infos;
+                    image_compression.pNext = nullptr;
+                    usage_info_pNext = &image_compression;
+                } break;
+
+                default:
+                    // Ignore all other info structs
+                    break;
+            }
+        }
+        gralloc_usage_info.pNext = usage_info_pNext;
+
+        result = dispatch.GetSwapchainGrallocUsage3ANDROID(
+            device, &gralloc_usage_info, &native_usage);
+        ATRACE_END();
         if (result != VK_SUCCESS) {
-            ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d",
-                result);
+            ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result);
             return VK_ERROR_SURFACE_LOST_KHR;
         }
+    } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) {
+        uint64_t consumer_usage, producer_usage;
+        ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID");
+        result = dispatch.GetSwapchainGrallocUsage2ANDROID(
+            device, create_info->imageFormat, create_info->imageUsage,
+            swapchain_image_usage, &consumer_usage, &producer_usage);
+        ATRACE_END();
+        if (result != VK_SUCCESS) {
+            ALOGE("vkGetSwapchainGrallocUsage2ANDROID failed: %d", result);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+        native_usage =
+            convertGralloc1ToBufferUsage(producer_usage, consumer_usage);
+    } else if (dispatch.GetSwapchainGrallocUsageANDROID) {
+        ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID");
+        int32_t legacy_usage = 0;
+        result = dispatch.GetSwapchainGrallocUsageANDROID(
+            device, create_info->imageFormat, create_info->imageUsage,
+            &legacy_usage);
+        ATRACE_END();
+        if (result != VK_SUCCESS) {
+            ALOGE("vkGetSwapchainGrallocUsageANDROID failed: %d", result);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+        native_usage = static_cast<uint64_t>(legacy_usage);
     }
-
-    *producer_usage = ahb_usage.androidHardwareBufferUsage;
+    *producer_usage = native_usage;
 
     return VK_SUCCESS;
 }
@@ -1590,8 +1659,8 @@
 
     PixelFormat native_pixel_format =
         GetNativePixelFormat(create_info->imageFormat);
-    DataSpace native_dataspace =
-        GetNativeDataspace(create_info->imageColorSpace, native_pixel_format);
+    DataSpace native_dataspace = GetNativeDataspace(
+        create_info->imageColorSpace, create_info->imageFormat);
     if (native_dataspace == DataSpace::UNKNOWN) {
         ALOGE(
             "CreateSwapchainKHR(VkSwapchainCreateInfoKHR.imageColorSpace = %d) "
@@ -1762,6 +1831,8 @@
     }
 
     int query_value;
+    // TODO: Now that we are calling into GPDSC2 directly, this query may be redundant
+    //       the call to std::max(min_buffer_count, num_images) may be redundant as well
     err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
                         &query_value);
     if (err != android::OK || query_value < 0) {
@@ -1778,12 +1849,33 @@
     // with extra images (which they can't actually use!).
     const uint32_t min_buffer_count = min_undequeued_buffers + 1;
 
-    uint32_t num_images;
-    if (create_info->presentMode  == VK_PRESENT_MODE_MAILBOX_KHR) {
-        num_images = std::max(3u, create_info->minImageCount);
-    } else {
-        num_images = create_info->minImageCount;
-    }
+    // Call into GPDSC2 to get the minimum and maximum allowable buffer count for the surface of
+    // interest. This step is only necessary if the app requests a number of images
+    // (create_info->minImageCount) that is less or more than the surface capabilities.
+    // An app should be calling GPDSC2 and using those values to set create_info, but in the
+    // event that the app has hard-coded image counts an error can occur
+    VkSurfacePresentModeEXT present_mode = {
+        VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT,
+        nullptr,
+        create_info->presentMode
+    };
+    VkPhysicalDeviceSurfaceInfo2KHR surface_info2 = {
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
+        &present_mode,
+        create_info->surface
+    };
+    VkSurfaceCapabilities2KHR surface_capabilities2 = {
+        VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
+        nullptr,
+        {},
+    };
+    result = GetPhysicalDeviceSurfaceCapabilities2KHR(GetData(device).driver_physical_device,
+            &surface_info2, &surface_capabilities2);
+
+    uint32_t num_images = create_info->minImageCount;
+    num_images = std::clamp(num_images,
+            surface_capabilities2.surfaceCapabilities.minImageCount,
+            surface_capabilities2.surfaceCapabilities.maxImageCount);
 
     const uint32_t buffer_count = std::max(min_buffer_count, num_images);
     err = native_window_set_buffer_count(window, buffer_count);
diff --git a/vulkan/vkprofiles/Android.bp b/vulkan/vkprofiles/Android.bp
new file mode 100644
index 0000000..94850cc
--- /dev/null
+++ b/vulkan/vkprofiles/Android.bp
@@ -0,0 +1,68 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_defaults {
+    name: "libvkprofiles_deps",
+    shared_libs: [
+        "libvulkan",
+    ],
+}
+
+cc_library_static {
+    name: "libvkprofiles",
+    defaults: [
+        "libvkprofiles_deps",
+    ],
+    srcs: [
+        "vkprofiles.cpp",
+        "generated/vulkan_profiles.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wimplicit-fallthrough",
+    ],
+    cppflags: [
+        "-Wno-error=unused-parameter",
+        "-Wno-error=missing-braces",
+        "-Wno-sign-compare",
+    ],
+    export_include_dirs: [
+        ".",
+    ],
+    export_shared_lib_headers: [
+        "libvulkan",
+    ],
+}
+
+cc_library_static {
+    name: "libvkprofiles_ndk",
+    srcs: [
+        "vkprofiles.cpp",
+        "generated/vulkan_profiles.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wimplicit-fallthrough",
+    ],
+    cppflags: [
+        "-Wno-error=unused-parameter",
+        "-Wno-error=missing-braces",
+        "-Wno-sign-compare",
+    ],
+    export_include_dirs: [
+        ".",
+    ],
+    header_libs: [
+        "vulkan_headers",
+    ],
+    sdk_version: "24",
+    stl: "libc++_static",
+}
diff --git a/vulkan/vkprofiles/README.md b/vulkan/vkprofiles/README.md
new file mode 100644
index 0000000..261089a
--- /dev/null
+++ b/vulkan/vkprofiles/README.md
@@ -0,0 +1,38 @@
+
+Get a local copy of the Vulkan-Profiles repository (https://github.com/KhronosGroup/Vulkan-Profiles/)
+
+NOTE: If the Vulkan-Headers you need for generation is later than the one that exists in
+`external/vulkan-headers`, then `external/vulkan-headers` will need to be updated to match.
+These updates to `external/vulkan` need to be made in AOSP. Changes to `ndk_translation` may
+need to be first made in internal-main.
+
+Run Vulkan-Profiles/scripts/gen_profiles_solutions.py in debug mode.
+
+Debug mode (at time of writing) requires a dedicated debug folder within the output-library location.
+~/Vulkan-Profiles$ mkdir debug
+~/Vulkan-Profiles$ python3 scripts/gen_profiles_solution.py --debug  --registry ~/<PATH_TO_YOUR_ANDROID_REPO>/external/vulkan-headers/registry/vk.xml --input ~/android/main/frameworks/native/vulkan/vkprofiles/profiles/ --output-library-inc . --output-library-src .
+
+Take the generated vulkan_profiles.h and vulkan_profiles.cpp from the debug directory you just created.
+
+~/Vulkan-Profiles$ cp debug/vulkan_profiles.cpp <PATH_TO_YOUR_ANDROID_REPO>/frameworks/native/vulkan/vkprofile/generated/
+~/Vulkan-Profiles$ cp debug/vulkan_profiles.h <PATH_TO_YOUR_ANDROID_REPO>/frameworks/native/vulkan/vkprofile/generated/
+
+
+The files need to be modified to land.
+1. Replace the generated license with the correct Android license
+(https://cs.android.com/android/platform/superproject/main/+/main:development/docs/copyright-templates/c.txt).
+Make sure to set the copyright to the current year. You should also remove the `This file is ***GENERATED***` part.
+2. Add VK_USE_PLATFORM_ANDROID_KHR between the license and the first includes for vulkan_profiles.cpp
+```
+ */
+
+#ifndef VK_USE_PLATFORM_ANDROID_KHR
+#define VK_USE_PLATFORM_ANDROID_KHR
+#endif
+
+#include ...
+```
+3. Rewrite the includes so that `vulkan_profiles.h` is correctly included
+4. Modify the #define `VP_DEBUG_MESSAGE_CALLBACK(MSG) ...` from "Profiles ERROR/WARNING" to "vkprofiles ERROR/WARNING"
+5. You may need to modify the Android.bp to remove warnings as errors, e.g. `"-Wno-error=unused-parameter",`
+6. Add `clang-format off` to the beginning and `clang-format on` to the end of the files
diff --git a/vulkan/vkprofiles/generated/.clang-format b/vulkan/vkprofiles/generated/.clang-format
new file mode 100644
index 0000000..fa1d429
--- /dev/null
+++ b/vulkan/vkprofiles/generated/.clang-format
@@ -0,0 +1,3 @@
+# Take the outputted source files from gen_profiles_solution.py and don't format
+DisableFormat: true
+SortIncludes: Never
diff --git a/vulkan/vkprofiles/generated/vulkan_profiles.cpp b/vulkan/vkprofiles/generated/vulkan_profiles.cpp
new file mode 100644
index 0000000..ce08b4e
--- /dev/null
+++ b/vulkan/vkprofiles/generated/vulkan_profiles.cpp
@@ -0,0 +1,11257 @@
+
+/*
+ * Copyright 2024 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.
+ *
+ */
+
+// clang-format off
+
+#ifndef VK_USE_PLATFORM_ANDROID_KHR
+#define VK_USE_PLATFORM_ANDROID_KHR
+#endif
+
+#include <cstddef>
+#include <cstdarg>
+#include <cstdio>
+#include <cstring>
+#include <cstdint>
+#include <cassert>
+#include <cmath>
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include <map>
+#include "vulkan_profiles.h"
+
+#include <cstdio>
+
+#ifndef VP_DEBUG_MESSAGE_CALLBACK
+#if defined(ANDROID) || defined(__ANDROID__)
+#include <android/log.h>
+#define VP_DEBUG_MESSAGE_CALLBACK(MSG)     __android_log_print(ANDROID_LOG_ERROR, "vkprofiles ERROR", "%s", MSG); \
+    __android_log_print(ANDROID_LOG_DEBUG, "vkprofiles WARNING", "%s", MSG)
+#else
+#define VP_DEBUG_MESSAGE_CALLBACK(MSG) fprintf(stderr, "%s\n", MSG)
+#endif
+#else
+void VP_DEBUG_MESSAGE_CALLBACK(const char*);
+#endif
+
+#define VP_DEBUG_MSG(MSG) VP_DEBUG_MESSAGE_CALLBACK(MSG)
+#define VP_DEBUG_MSGF(MSGFMT, ...) { char msg[1024]; snprintf(msg, sizeof(msg) - 1, (MSGFMT), __VA_ARGS__); VP_DEBUG_MESSAGE_CALLBACK(msg); }
+#define VP_DEBUG_COND_MSG(COND, MSG) if (COND) VP_DEBUG_MSG(MSG)
+#define VP_DEBUG_COND_MSGF(COND, MSGFMT, ...) if (COND) VP_DEBUG_MSGF(MSGFMT, __VA_ARGS__)
+
+#include <string>
+
+namespace detail {
+
+VPAPI_ATTR std::string vpGetDeviceAndDriverInfoString(VkPhysicalDevice physicalDevice,
+                                                      PFN_vkGetPhysicalDeviceProperties2KHR pfnGetPhysicalDeviceProperties2) {
+    VkPhysicalDeviceDriverPropertiesKHR driverProps{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR };
+    VkPhysicalDeviceProperties2KHR deviceProps{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR, &driverProps };
+    pfnGetPhysicalDeviceProperties2(physicalDevice, &deviceProps);
+    return std::string("deviceName=") + std::string(&deviceProps.properties.deviceName[0])
+                    + ", driverName=" + std::string(&driverProps.driverName[0])
+                    + ", driverInfo=" + std::string(&driverProps.driverInfo[0]);
+}
+
+}
+
+namespace detail {
+
+
+VPAPI_ATTR std::string FormatString(const char* message, ...) {
+    std::size_t const STRING_BUFFER(4096);
+
+    assert(message != nullptr);
+    assert(strlen(message) >= 1 && strlen(message) < STRING_BUFFER);
+
+    char buffer[STRING_BUFFER];
+    va_list list;
+
+    va_start(list, message);
+    vsnprintf(buffer, STRING_BUFFER, message, list);
+    va_end(list);
+
+    return buffer;
+}
+
+VPAPI_ATTR const void* vpGetStructure(const void* pNext, VkStructureType type) {
+    const VkBaseOutStructure* p = static_cast<const VkBaseOutStructure*>(pNext);
+    while (p != nullptr) {
+        if (p->sType == type) return p;
+        p = p->pNext;
+    }
+    return nullptr;
+}
+
+VPAPI_ATTR void* vpGetStructure(void* pNext, VkStructureType type) {
+    VkBaseOutStructure* p = static_cast<VkBaseOutStructure*>(pNext);
+    while (p != nullptr) {
+        if (p->sType == type) return p;
+        p = p->pNext;
+    }
+    return nullptr;
+}
+
+VPAPI_ATTR VkBaseOutStructure* vpExtractStructure(VkPhysicalDeviceFeatures2KHR* pFeatures, VkStructureType structureType) {
+    if (structureType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR) {
+        return nullptr;
+    }
+
+    VkBaseOutStructure* current = reinterpret_cast<VkBaseOutStructure*>(pFeatures);
+    VkBaseOutStructure* previous = nullptr;
+    VkBaseOutStructure* found = nullptr;
+
+    while (current != nullptr) {
+        if (structureType == current->sType) {
+            found = current;
+            if (previous != nullptr) {
+                previous->pNext = current->pNext;
+            }
+            current = nullptr;
+        } else {
+            previous = current;
+            current = current->pNext;
+        }
+    }
+
+    if (found != nullptr) {
+        found->pNext = nullptr;
+        return found;
+    } else {
+        return nullptr;
+    }
+}
+
+VPAPI_ATTR void GatherStructureTypes(std::vector<VkStructureType>& structureTypes, VkBaseOutStructure* pNext) {
+    while (pNext) {
+        if (std::find(structureTypes.begin(), structureTypes.end(), pNext->sType) == structureTypes.end()) {
+            structureTypes.push_back(pNext->sType);
+        }
+
+        pNext = pNext->pNext;
+    }
+}
+
+VPAPI_ATTR bool isMultiple(double source, double multiple) {
+    double mod = std::fmod(source, multiple);
+    return std::abs(mod) < 0.0001; 
+}
+
+VPAPI_ATTR bool isPowerOfTwo(double source) {
+    double mod = std::fmod(source, 1.0);
+    if (std::abs(mod) >= 0.0001) return false;
+
+    std::uint64_t value = static_cast<std::uint64_t>(std::abs(source));
+    return !(value & (value - static_cast<std::uint64_t>(1)));
+}
+
+using PFN_vpStructFiller = void(*)(VkBaseOutStructure* p);
+using PFN_vpStructComparator = bool(*)(VkBaseOutStructure* p);
+using PFN_vpStructChainerCb =  void(*)(VkBaseOutStructure* p, void* pUser);
+using PFN_vpStructChainer = void(*)(VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb);
+
+struct VpFeatureDesc {
+    PFN_vpStructFiller              pfnFiller;
+    PFN_vpStructComparator          pfnComparator;
+};
+
+struct VpPropertyDesc {
+    PFN_vpStructFiller              pfnFiller;
+    PFN_vpStructComparator          pfnComparator;
+};
+
+struct VpQueueFamilyDesc {
+    PFN_vpStructFiller              pfnFiller;
+    PFN_vpStructComparator          pfnComparator;
+};
+
+struct VpFormatDesc {
+    VkFormat                        format;
+    PFN_vpStructFiller              pfnFiller;
+    PFN_vpStructComparator          pfnComparator;
+};
+
+struct VpStructChainerDesc {
+    PFN_vpStructChainer             pfnFeature;
+    PFN_vpStructChainer             pfnProperty;
+    PFN_vpStructChainer             pfnQueueFamily;
+    PFN_vpStructChainer             pfnFormat;
+};
+
+struct VpVariantDesc {
+    char blockName[VP_MAX_PROFILE_NAME_SIZE];
+
+    uint32_t instanceExtensionCount;
+    const VkExtensionProperties* pInstanceExtensions;
+
+    uint32_t deviceExtensionCount;
+    const VkExtensionProperties* pDeviceExtensions;
+
+    uint32_t featureStructTypeCount;
+    const VkStructureType* pFeatureStructTypes;
+    VpFeatureDesc feature;
+
+    uint32_t propertyStructTypeCount;
+    const VkStructureType* pPropertyStructTypes;
+    VpPropertyDesc property;
+
+    uint32_t queueFamilyStructTypeCount;
+    const VkStructureType* pQueueFamilyStructTypes;
+    uint32_t queueFamilyCount;
+    const VpQueueFamilyDesc* pQueueFamilies;
+
+    uint32_t formatStructTypeCount;
+    const VkStructureType* pFormatStructTypes;
+    uint32_t formatCount;
+    const VpFormatDesc* pFormats;
+
+    VpStructChainerDesc chainers;
+};
+
+struct VpCapabilitiesDesc {
+    uint32_t variantCount;
+    const VpVariantDesc* pVariants;
+};
+
+struct VpProfileDesc {
+    VpProfileProperties             props;
+    uint32_t                        minApiVersion;
+
+    const detail::VpVariantDesc*    pMergedCapabilities;
+    
+    uint32_t                        requiredProfileCount;
+    const VpProfileProperties*      pRequiredProfiles;
+
+    uint32_t                        requiredCapabilityCount;
+    const VpCapabilitiesDesc*       pRequiredCapabilities;
+
+    uint32_t                        fallbackCount;
+    const VpProfileProperties*      pFallbacks;
+};
+
+template <typename T>
+VPAPI_ATTR bool vpCheckFlags(const T& actual, const uint64_t expected) {
+    return (actual & expected) == expected;
+}
+#ifdef VP_ANDROID_15_minimums
+namespace VP_ANDROID_15_MINIMUMS {
+
+static const VkStructureType featureStructTypes[] = {
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG,
+};
+
+static const VkStructureType propertyStructTypes[] = {
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES,
+};
+
+static const VkStructureType formatStructTypes[] = {
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR,
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR,
+};
+
+namespace MUST {
+static const VkExtensionProperties instanceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME, 1 },
+};
+
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_ANDROID_EXTERNAL_FORMAT_RESOLVE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_4444_FORMATS_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_EXTERNAL_MEMORY_ACQUIRE_UNMODIFIED_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_16BIT_STORAGE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_MAINTENANCE_5_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* s = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    s->features.drawIndirectFirstInstance = VK_TRUE;
+                    s->features.samplerAnisotropy = VK_TRUE;
+                    s->features.shaderImageGatherExtended = VK_TRUE;
+                    s->features.shaderStorageImageExtendedFormats = VK_TRUE;
+                    s->features.shaderStorageImageReadWithoutFormat = VK_TRUE;
+                    s->features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: {
+                    VkPhysicalDeviceVulkan12Features* s = static_cast<VkPhysicalDeviceVulkan12Features*>(static_cast<void*>(p));
+                    s->shaderFloat16 = VK_TRUE;
+                    s->shaderInt8 = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT: {
+                    VkPhysicalDeviceCustomBorderColorFeaturesEXT* s = static_cast<VkPhysicalDeviceCustomBorderColorFeaturesEXT*>(static_cast<void*>(p));
+                    s->customBorderColors = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT: {
+                    VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT* s = static_cast<VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT*>(static_cast<void*>(p));
+                    s->primitiveTopologyListRestart = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT: {
+                    VkPhysicalDeviceProvokingVertexFeaturesEXT* s = static_cast<VkPhysicalDeviceProvokingVertexFeaturesEXT*>(static_cast<void*>(p));
+                    s->provokingVertexLast = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT: {
+                    VkPhysicalDeviceIndexTypeUint8FeaturesEXT* s = static_cast<VkPhysicalDeviceIndexTypeUint8FeaturesEXT*>(static_cast<void*>(p));
+                    s->indexTypeUint8 = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR: {
+                    VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR* s = static_cast<VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR*>(static_cast<void*>(p));
+                    s->vertexAttributeInstanceRateDivisor = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: {
+                    VkPhysicalDeviceSamplerYcbcrConversionFeatures* s = static_cast<VkPhysicalDeviceSamplerYcbcrConversionFeatures*>(static_cast<void*>(p));
+                    s->samplerYcbcrConversion = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES: {
+                    VkPhysicalDeviceShaderFloat16Int8Features* s = static_cast<VkPhysicalDeviceShaderFloat16Int8Features*>(static_cast<void*>(p));
+                    s->shaderFloat16 = VK_TRUE;
+                    s->shaderInt8 = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES: {
+                    VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures* s = static_cast<VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures*>(static_cast<void*>(p));
+                    s->shaderSubgroupExtendedTypes = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES: {
+                    VkPhysicalDevice8BitStorageFeatures* s = static_cast<VkPhysicalDevice8BitStorageFeatures*>(static_cast<void*>(p));
+                    s->storageBuffer8BitAccess = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES: {
+                    VkPhysicalDevice16BitStorageFeatures* s = static_cast<VkPhysicalDevice16BitStorageFeatures*>(static_cast<void*>(p));
+                    s->storageBuffer16BitAccess = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* prettify_VkPhysicalDeviceFeatures2KHR = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.drawIndirectFirstInstance == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.drawIndirectFirstInstance == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.drawIndirectFirstInstance == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.samplerAnisotropy == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.samplerAnisotropy == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.samplerAnisotropy == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderImageGatherExtended == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderImageGatherExtended == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderImageGatherExtended == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageExtendedFormats == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageExtendedFormats == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageExtendedFormats == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageReadWithoutFormat == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageReadWithoutFormat == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageReadWithoutFormat == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageWriteWithoutFormat == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageWriteWithoutFormat == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageWriteWithoutFormat == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: {
+                    VkPhysicalDeviceVulkan12Features* prettify_VkPhysicalDeviceVulkan12Features = static_cast<VkPhysicalDeviceVulkan12Features*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceVulkan12Features->shaderFloat16 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceVulkan12Features->shaderFloat16 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceVulkan12Features::shaderFloat16 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceVulkan12Features->shaderInt8 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceVulkan12Features->shaderInt8 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceVulkan12Features::shaderInt8 == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT: {
+                    VkPhysicalDeviceCustomBorderColorFeaturesEXT* prettify_VkPhysicalDeviceCustomBorderColorFeaturesEXT = static_cast<VkPhysicalDeviceCustomBorderColorFeaturesEXT*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceCustomBorderColorFeaturesEXT->customBorderColors == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceCustomBorderColorFeaturesEXT->customBorderColors == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceCustomBorderColorFeaturesEXT::customBorderColors == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT: {
+                    VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT* prettify_VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT = static_cast<VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT->primitiveTopologyListRestart == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT->primitiveTopologyListRestart == VK_TRUE), "Unsupported feature condition: VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT::primitiveTopologyListRestart == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT: {
+                    VkPhysicalDeviceProvokingVertexFeaturesEXT* prettify_VkPhysicalDeviceProvokingVertexFeaturesEXT = static_cast<VkPhysicalDeviceProvokingVertexFeaturesEXT*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceProvokingVertexFeaturesEXT->provokingVertexLast == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProvokingVertexFeaturesEXT->provokingVertexLast == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceProvokingVertexFeaturesEXT::provokingVertexLast == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT: {
+                    VkPhysicalDeviceIndexTypeUint8FeaturesEXT* prettify_VkPhysicalDeviceIndexTypeUint8FeaturesEXT = static_cast<VkPhysicalDeviceIndexTypeUint8FeaturesEXT*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceIndexTypeUint8FeaturesEXT->indexTypeUint8 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceIndexTypeUint8FeaturesEXT->indexTypeUint8 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceIndexTypeUint8FeaturesEXT::indexTypeUint8 == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR: {
+                    VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR* prettify_VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR = static_cast<VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR->vertexAttributeInstanceRateDivisor == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR->vertexAttributeInstanceRateDivisor == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR::vertexAttributeInstanceRateDivisor == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: {
+                    VkPhysicalDeviceSamplerYcbcrConversionFeatures* prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures = static_cast<VkPhysicalDeviceSamplerYcbcrConversionFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures->samplerYcbcrConversion == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures->samplerYcbcrConversion == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceSamplerYcbcrConversionFeatures::samplerYcbcrConversion == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES: {
+                    VkPhysicalDeviceShaderFloat16Int8Features* prettify_VkPhysicalDeviceShaderFloat16Int8Features = static_cast<VkPhysicalDeviceShaderFloat16Int8Features*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceShaderFloat16Int8Features->shaderFloat16 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceShaderFloat16Int8Features->shaderFloat16 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceShaderFloat16Int8Features::shaderFloat16 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceShaderFloat16Int8Features->shaderInt8 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceShaderFloat16Int8Features->shaderInt8 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceShaderFloat16Int8Features::shaderInt8 == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES: {
+                    VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures* prettify_VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures = static_cast<VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures->shaderSubgroupExtendedTypes == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures->shaderSubgroupExtendedTypes == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures::shaderSubgroupExtendedTypes == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES: {
+                    VkPhysicalDevice8BitStorageFeatures* prettify_VkPhysicalDevice8BitStorageFeatures = static_cast<VkPhysicalDevice8BitStorageFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDevice8BitStorageFeatures->storageBuffer8BitAccess == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDevice8BitStorageFeatures->storageBuffer8BitAccess == VK_TRUE), "Unsupported feature condition: VkPhysicalDevice8BitStorageFeatures::storageBuffer8BitAccess == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES: {
+                    VkPhysicalDevice16BitStorageFeatures* prettify_VkPhysicalDevice16BitStorageFeatures = static_cast<VkPhysicalDevice16BitStorageFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDevice16BitStorageFeatures->storageBuffer16BitAccess == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDevice16BitStorageFeatures->storageBuffer16BitAccess == VK_TRUE), "Unsupported feature condition: VkPhysicalDevice16BitStorageFeatures::storageBuffer16BitAccess == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR: {
+                    VkPhysicalDeviceProperties2KHR* s = static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p));
+                    s->properties.limits.maxColorAttachments = 8;
+                    s->properties.limits.maxPerStageDescriptorSampledImages = 128;
+                    s->properties.limits.maxPerStageDescriptorSamplers = 128;
+                    s->properties.limits.maxPerStageDescriptorStorageBuffers = 12;
+                    s->properties.limits.maxPerStageDescriptorUniformBuffers = 13;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES: {
+                    VkPhysicalDeviceVulkan11Properties* s = static_cast<VkPhysicalDeviceVulkan11Properties*>(static_cast<void*>(p));
+                    s->subgroupSupportedOperations |= (VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_VOTE_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT);
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR: {
+                    VkPhysicalDeviceProperties2KHR* prettify_VkPhysicalDeviceProperties2KHR = static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxColorAttachments >= 8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxColorAttachments >= 8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxColorAttachments >= 8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSampledImages >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSampledImages >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorSampledImages >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSamplers >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSamplers >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorSamplers >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageBuffers >= 12); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageBuffers >= 12), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorStorageBuffers >= 12");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorUniformBuffers >= 13); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorUniformBuffers >= 13), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorUniformBuffers >= 13");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES: {
+                    VkPhysicalDeviceVulkan11Properties* prettify_VkPhysicalDeviceVulkan11Properties = static_cast<VkPhysicalDeviceVulkan11Properties*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceVulkan11Properties->subgroupSupportedOperations, (VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_VOTE_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceVulkan11Properties->subgroupSupportedOperations, (VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_VOTE_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT))), "Unsupported properties condition: VkPhysicalDeviceVulkan11Properties::subgroupSupportedOperations contains (VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_VOTE_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT)");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpFormatDesc formatDesc[] = {
+    {
+        VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan12Features physicalDeviceVulkan12Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, nullptr };
+        VkPhysicalDeviceCustomBorderColorFeaturesEXT physicalDeviceCustomBorderColorFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT, &physicalDeviceVulkan12Features };
+        VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT physicalDevicePrimitiveTopologyListRestartFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, &physicalDeviceCustomBorderColorFeaturesEXT };
+        VkPhysicalDeviceProvokingVertexFeaturesEXT physicalDeviceProvokingVertexFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, &physicalDevicePrimitiveTopologyListRestartFeaturesEXT };
+        VkPhysicalDeviceIndexTypeUint8FeaturesEXT physicalDeviceIndexTypeUint8FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT, &physicalDeviceProvokingVertexFeaturesEXT };
+        VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR physicalDeviceVertexAttributeDivisorFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR, &physicalDeviceIndexTypeUint8FeaturesEXT };
+        VkPhysicalDeviceSamplerYcbcrConversionFeatures physicalDeviceSamplerYcbcrConversionFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, &physicalDeviceVertexAttributeDivisorFeaturesKHR };
+        VkPhysicalDeviceShaderFloat16Int8Features physicalDeviceShaderFloat16Int8Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, &physicalDeviceSamplerYcbcrConversionFeatures };
+        VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures physicalDeviceShaderSubgroupExtendedTypesFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES, &physicalDeviceShaderFloat16Int8Features };
+        VkPhysicalDevice8BitStorageFeatures physicalDevice8BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, &physicalDeviceShaderSubgroupExtendedTypesFeatures };
+        VkPhysicalDevice16BitStorageFeatures physicalDevice16BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, &physicalDevice8BitStorageFeatures };
+        VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT physicalDevicePrimitivesGeneratedQueryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT, &physicalDevice16BitStorageFeatures };
+        VkPhysicalDeviceLineRasterizationFeaturesEXT physicalDeviceLineRasterizationFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, &physicalDevicePrimitivesGeneratedQueryFeaturesEXT };
+        VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG physicalDeviceRelaxedLineRasterizationFeaturesIMG{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG, &physicalDeviceLineRasterizationFeaturesEXT };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceRelaxedLineRasterizationFeaturesIMG));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan11Properties physicalDeviceVulkan11Properties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceVulkan11Properties));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+} //namespace MUST
+namespace primitivesGeneratedQuery {
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT: {
+                    VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT* s = static_cast<VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT*>(static_cast<void*>(p));
+                    s->primitivesGeneratedQuery = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT: {
+                    VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT* prettify_VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT = static_cast<VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT->primitivesGeneratedQuery == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT->primitivesGeneratedQuery == VK_TRUE), "Unsupported feature condition: VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT::primitivesGeneratedQuery == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+        return ret;
+    }
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan12Features physicalDeviceVulkan12Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, nullptr };
+        VkPhysicalDeviceCustomBorderColorFeaturesEXT physicalDeviceCustomBorderColorFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT, &physicalDeviceVulkan12Features };
+        VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT physicalDevicePrimitiveTopologyListRestartFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, &physicalDeviceCustomBorderColorFeaturesEXT };
+        VkPhysicalDeviceProvokingVertexFeaturesEXT physicalDeviceProvokingVertexFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, &physicalDevicePrimitiveTopologyListRestartFeaturesEXT };
+        VkPhysicalDeviceIndexTypeUint8FeaturesEXT physicalDeviceIndexTypeUint8FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT, &physicalDeviceProvokingVertexFeaturesEXT };
+        VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR physicalDeviceVertexAttributeDivisorFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR, &physicalDeviceIndexTypeUint8FeaturesEXT };
+        VkPhysicalDeviceSamplerYcbcrConversionFeatures physicalDeviceSamplerYcbcrConversionFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, &physicalDeviceVertexAttributeDivisorFeaturesKHR };
+        VkPhysicalDeviceShaderFloat16Int8Features physicalDeviceShaderFloat16Int8Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, &physicalDeviceSamplerYcbcrConversionFeatures };
+        VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures physicalDeviceShaderSubgroupExtendedTypesFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES, &physicalDeviceShaderFloat16Int8Features };
+        VkPhysicalDevice8BitStorageFeatures physicalDevice8BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, &physicalDeviceShaderSubgroupExtendedTypesFeatures };
+        VkPhysicalDevice16BitStorageFeatures physicalDevice16BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, &physicalDevice8BitStorageFeatures };
+        VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT physicalDevicePrimitivesGeneratedQueryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT, &physicalDevice16BitStorageFeatures };
+        VkPhysicalDeviceLineRasterizationFeaturesEXT physicalDeviceLineRasterizationFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, &physicalDevicePrimitivesGeneratedQueryFeaturesEXT };
+        VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG physicalDeviceRelaxedLineRasterizationFeaturesIMG{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG, &physicalDeviceLineRasterizationFeaturesEXT };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceRelaxedLineRasterizationFeaturesIMG));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan11Properties physicalDeviceVulkan11Properties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceVulkan11Properties));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+} //namespace primitivesGeneratedQuery
+namespace pipelineStatisticsQuery {
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* s = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    s->features.pipelineStatisticsQuery = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* prettify_VkPhysicalDeviceFeatures2KHR = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.pipelineStatisticsQuery == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.pipelineStatisticsQuery == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.pipelineStatisticsQuery == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+        return ret;
+    }
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan12Features physicalDeviceVulkan12Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, nullptr };
+        VkPhysicalDeviceCustomBorderColorFeaturesEXT physicalDeviceCustomBorderColorFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT, &physicalDeviceVulkan12Features };
+        VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT physicalDevicePrimitiveTopologyListRestartFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, &physicalDeviceCustomBorderColorFeaturesEXT };
+        VkPhysicalDeviceProvokingVertexFeaturesEXT physicalDeviceProvokingVertexFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, &physicalDevicePrimitiveTopologyListRestartFeaturesEXT };
+        VkPhysicalDeviceIndexTypeUint8FeaturesEXT physicalDeviceIndexTypeUint8FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT, &physicalDeviceProvokingVertexFeaturesEXT };
+        VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR physicalDeviceVertexAttributeDivisorFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR, &physicalDeviceIndexTypeUint8FeaturesEXT };
+        VkPhysicalDeviceSamplerYcbcrConversionFeatures physicalDeviceSamplerYcbcrConversionFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, &physicalDeviceVertexAttributeDivisorFeaturesKHR };
+        VkPhysicalDeviceShaderFloat16Int8Features physicalDeviceShaderFloat16Int8Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, &physicalDeviceSamplerYcbcrConversionFeatures };
+        VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures physicalDeviceShaderSubgroupExtendedTypesFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES, &physicalDeviceShaderFloat16Int8Features };
+        VkPhysicalDevice8BitStorageFeatures physicalDevice8BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, &physicalDeviceShaderSubgroupExtendedTypesFeatures };
+        VkPhysicalDevice16BitStorageFeatures physicalDevice16BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, &physicalDevice8BitStorageFeatures };
+        VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT physicalDevicePrimitivesGeneratedQueryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT, &physicalDevice16BitStorageFeatures };
+        VkPhysicalDeviceLineRasterizationFeaturesEXT physicalDeviceLineRasterizationFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, &physicalDevicePrimitivesGeneratedQueryFeaturesEXT };
+        VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG physicalDeviceRelaxedLineRasterizationFeaturesIMG{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG, &physicalDeviceLineRasterizationFeaturesEXT };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceRelaxedLineRasterizationFeaturesIMG));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan11Properties physicalDeviceVulkan11Properties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceVulkan11Properties));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+} //namespace pipelineStatisticsQuery
+namespace swBresenhamLines {
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT: {
+                    VkPhysicalDeviceLineRasterizationFeaturesEXT* s = static_cast<VkPhysicalDeviceLineRasterizationFeaturesEXT*>(static_cast<void*>(p));
+                    s->bresenhamLines = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT: {
+                    VkPhysicalDeviceLineRasterizationFeaturesEXT* prettify_VkPhysicalDeviceLineRasterizationFeaturesEXT = static_cast<VkPhysicalDeviceLineRasterizationFeaturesEXT*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceLineRasterizationFeaturesEXT->bresenhamLines == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceLineRasterizationFeaturesEXT->bresenhamLines == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceLineRasterizationFeaturesEXT::bresenhamLines == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+        return ret;
+    }
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan12Features physicalDeviceVulkan12Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, nullptr };
+        VkPhysicalDeviceCustomBorderColorFeaturesEXT physicalDeviceCustomBorderColorFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT, &physicalDeviceVulkan12Features };
+        VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT physicalDevicePrimitiveTopologyListRestartFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, &physicalDeviceCustomBorderColorFeaturesEXT };
+        VkPhysicalDeviceProvokingVertexFeaturesEXT physicalDeviceProvokingVertexFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, &physicalDevicePrimitiveTopologyListRestartFeaturesEXT };
+        VkPhysicalDeviceIndexTypeUint8FeaturesEXT physicalDeviceIndexTypeUint8FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT, &physicalDeviceProvokingVertexFeaturesEXT };
+        VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR physicalDeviceVertexAttributeDivisorFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR, &physicalDeviceIndexTypeUint8FeaturesEXT };
+        VkPhysicalDeviceSamplerYcbcrConversionFeatures physicalDeviceSamplerYcbcrConversionFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, &physicalDeviceVertexAttributeDivisorFeaturesKHR };
+        VkPhysicalDeviceShaderFloat16Int8Features physicalDeviceShaderFloat16Int8Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, &physicalDeviceSamplerYcbcrConversionFeatures };
+        VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures physicalDeviceShaderSubgroupExtendedTypesFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES, &physicalDeviceShaderFloat16Int8Features };
+        VkPhysicalDevice8BitStorageFeatures physicalDevice8BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, &physicalDeviceShaderSubgroupExtendedTypesFeatures };
+        VkPhysicalDevice16BitStorageFeatures physicalDevice16BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, &physicalDevice8BitStorageFeatures };
+        VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT physicalDevicePrimitivesGeneratedQueryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT, &physicalDevice16BitStorageFeatures };
+        VkPhysicalDeviceLineRasterizationFeaturesEXT physicalDeviceLineRasterizationFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, &physicalDevicePrimitivesGeneratedQueryFeaturesEXT };
+        VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG physicalDeviceRelaxedLineRasterizationFeaturesIMG{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG, &physicalDeviceLineRasterizationFeaturesEXT };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceRelaxedLineRasterizationFeaturesIMG));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan11Properties physicalDeviceVulkan11Properties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceVulkan11Properties));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+} //namespace swBresenhamLines
+namespace hwBresenhamLines {
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_IMG_RELAXED_LINE_RASTERIZATION_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG: {
+                    VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG* s = static_cast<VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG*>(static_cast<void*>(p));
+                    s->relaxedLineRasterization = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG: {
+                    VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG* prettify_VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG = static_cast<VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG->relaxedLineRasterization == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG->relaxedLineRasterization == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG::relaxedLineRasterization == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+        return ret;
+    }
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan12Features physicalDeviceVulkan12Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, nullptr };
+        VkPhysicalDeviceCustomBorderColorFeaturesEXT physicalDeviceCustomBorderColorFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT, &physicalDeviceVulkan12Features };
+        VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT physicalDevicePrimitiveTopologyListRestartFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, &physicalDeviceCustomBorderColorFeaturesEXT };
+        VkPhysicalDeviceProvokingVertexFeaturesEXT physicalDeviceProvokingVertexFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, &physicalDevicePrimitiveTopologyListRestartFeaturesEXT };
+        VkPhysicalDeviceIndexTypeUint8FeaturesEXT physicalDeviceIndexTypeUint8FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT, &physicalDeviceProvokingVertexFeaturesEXT };
+        VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR physicalDeviceVertexAttributeDivisorFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR, &physicalDeviceIndexTypeUint8FeaturesEXT };
+        VkPhysicalDeviceSamplerYcbcrConversionFeatures physicalDeviceSamplerYcbcrConversionFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, &physicalDeviceVertexAttributeDivisorFeaturesKHR };
+        VkPhysicalDeviceShaderFloat16Int8Features physicalDeviceShaderFloat16Int8Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, &physicalDeviceSamplerYcbcrConversionFeatures };
+        VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures physicalDeviceShaderSubgroupExtendedTypesFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES, &physicalDeviceShaderFloat16Int8Features };
+        VkPhysicalDevice8BitStorageFeatures physicalDevice8BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, &physicalDeviceShaderSubgroupExtendedTypesFeatures };
+        VkPhysicalDevice16BitStorageFeatures physicalDevice16BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, &physicalDevice8BitStorageFeatures };
+        VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT physicalDevicePrimitivesGeneratedQueryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT, &physicalDevice16BitStorageFeatures };
+        VkPhysicalDeviceLineRasterizationFeaturesEXT physicalDeviceLineRasterizationFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, &physicalDevicePrimitivesGeneratedQueryFeaturesEXT };
+        VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG physicalDeviceRelaxedLineRasterizationFeaturesIMG{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG, &physicalDeviceLineRasterizationFeaturesEXT };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceRelaxedLineRasterizationFeaturesIMG));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceVulkan11Properties physicalDeviceVulkan11Properties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceVulkan11Properties));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+} //namespace hwBresenhamLines
+} // namespace VP_ANDROID_15_MINIMUMS
+#endif // VP_ANDROID_15_minimums
+
+#ifdef VP_ANDROID_baseline_2021
+namespace VP_ANDROID_BASELINE_2021 {
+
+static const VkStructureType featureStructTypes[] = {
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
+};
+
+static const VkStructureType propertyStructTypes[] = {
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
+};
+
+static const VkStructureType formatStructTypes[] = {
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR,
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR,
+};
+
+static const VkExtensionProperties instanceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SURFACE_EXTENSION_NAME, 1 },
+};
+
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_MAINTENANCE_1_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SWAPCHAIN_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_VARIABLE_POINTERS_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* s = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    s->features.depthBiasClamp = VK_TRUE;
+                    s->features.fragmentStoresAndAtomics = VK_TRUE;
+                    s->features.fullDrawIndexUint32 = VK_TRUE;
+                    s->features.imageCubeArray = VK_TRUE;
+                    s->features.independentBlend = VK_TRUE;
+                    s->features.robustBufferAccess = VK_TRUE;
+                    s->features.sampleRateShading = VK_TRUE;
+                    s->features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderStorageImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE;
+                    s->features.textureCompressionASTC_LDR = VK_TRUE;
+                    s->features.textureCompressionETC2 = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* prettify_VkPhysicalDeviceFeatures2KHR = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.depthBiasClamp == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fragmentStoresAndAtomics == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fullDrawIndexUint32 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.imageCubeArray == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.independentBlend == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.robustBufferAccess == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.sampleRateShading == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderSampledImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionASTC_LDR == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionETC2 == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+        return ret;
+    }
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(nullptr));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(nullptr));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+
+namespace baseline {
+static const VkExtensionProperties instanceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SURFACE_EXTENSION_NAME, 1 },
+};
+
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_MAINTENANCE_1_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SWAPCHAIN_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_VARIABLE_POINTERS_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* s = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    s->features.depthBiasClamp = VK_TRUE;
+                    s->features.fragmentStoresAndAtomics = VK_TRUE;
+                    s->features.fullDrawIndexUint32 = VK_TRUE;
+                    s->features.imageCubeArray = VK_TRUE;
+                    s->features.independentBlend = VK_TRUE;
+                    s->features.robustBufferAccess = VK_TRUE;
+                    s->features.sampleRateShading = VK_TRUE;
+                    s->features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderStorageImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE;
+                    s->features.textureCompressionASTC_LDR = VK_TRUE;
+                    s->features.textureCompressionETC2 = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* prettify_VkPhysicalDeviceFeatures2KHR = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.depthBiasClamp == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fragmentStoresAndAtomics == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fullDrawIndexUint32 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.imageCubeArray == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.independentBlend == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.robustBufferAccess == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.sampleRateShading == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderSampledImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionASTC_LDR == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionETC2 == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR: {
+                    VkPhysicalDeviceProperties2KHR* s = static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p));
+                    s->properties.limits.discreteQueuePriorities = 2;
+                    s->properties.limits.framebufferColorSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferDepthSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferNoAttachmentsSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferStencilSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.maxBoundDescriptorSets = 4;
+                    s->properties.limits.maxColorAttachments = 4;
+                    s->properties.limits.maxComputeSharedMemorySize = 16384;
+                    s->properties.limits.maxComputeWorkGroupCount[0] = 65535;
+                    s->properties.limits.maxComputeWorkGroupCount[1] = 65535;
+                    s->properties.limits.maxComputeWorkGroupCount[2] = 65535;
+                    s->properties.limits.maxComputeWorkGroupInvocations = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[0] = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[1] = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[2] = 64;
+                    s->properties.limits.maxDescriptorSetInputAttachments = 4;
+                    s->properties.limits.maxDescriptorSetSampledImages = 48;
+                    s->properties.limits.maxDescriptorSetSamplers = 48;
+                    s->properties.limits.maxDescriptorSetStorageBuffers = 24;
+                    s->properties.limits.maxDescriptorSetStorageBuffersDynamic = 4;
+                    s->properties.limits.maxDescriptorSetStorageImages = 12;
+                    s->properties.limits.maxDescriptorSetUniformBuffers = 36;
+                    s->properties.limits.maxDescriptorSetUniformBuffersDynamic = 8;
+                    s->properties.limits.maxDrawIndexedIndexValue = 4294967295;
+                    s->properties.limits.maxDrawIndirectCount = 1;
+                    s->properties.limits.maxFragmentCombinedOutputResources = 8;
+                    s->properties.limits.maxFragmentInputComponents = 64;
+                    s->properties.limits.maxFragmentOutputAttachments = 4;
+                    s->properties.limits.maxFramebufferHeight = 4096;
+                    s->properties.limits.maxFramebufferLayers = 256;
+                    s->properties.limits.maxFramebufferWidth = 4096;
+                    s->properties.limits.maxImageArrayLayers = 256;
+                    s->properties.limits.maxImageDimension1D = 4096;
+                    s->properties.limits.maxImageDimension2D = 4096;
+                    s->properties.limits.maxImageDimension3D = 512;
+                    s->properties.limits.maxImageDimensionCube = 4096;
+                    s->properties.limits.maxInterpolationOffset = 0.4375f;
+                    s->properties.limits.maxMemoryAllocationCount = 4096;
+                    s->properties.limits.maxPerStageDescriptorInputAttachments = 4;
+                    s->properties.limits.maxPerStageDescriptorSampledImages = 16;
+                    s->properties.limits.maxPerStageDescriptorSamplers = 16;
+                    s->properties.limits.maxPerStageDescriptorStorageBuffers = 4;
+                    s->properties.limits.maxPerStageDescriptorStorageImages = 4;
+                    s->properties.limits.maxPerStageDescriptorUniformBuffers = 12;
+                    s->properties.limits.maxPerStageResources = 44;
+                    s->properties.limits.maxPushConstantsSize = 128;
+                    s->properties.limits.maxSampleMaskWords = 1;
+                    s->properties.limits.maxSamplerAllocationCount = 4000;
+                    s->properties.limits.maxSamplerAnisotropy = 1.0f;
+                    s->properties.limits.maxSamplerLodBias = 2.0f;
+                    s->properties.limits.maxStorageBufferRange = 134217728;
+                    s->properties.limits.maxTexelBufferElements = 65536;
+                    s->properties.limits.maxTexelOffset = 7;
+                    s->properties.limits.maxUniformBufferRange = 16384;
+                    s->properties.limits.maxVertexInputAttributeOffset = 2047;
+                    s->properties.limits.maxVertexInputAttributes = 16;
+                    s->properties.limits.maxVertexInputBindingStride = 2048;
+                    s->properties.limits.maxVertexInputBindings = 16;
+                    s->properties.limits.maxVertexOutputComponents = 64;
+                    s->properties.limits.maxViewportDimensions[0] = 4096;
+                    s->properties.limits.maxViewportDimensions[1] = 4096;
+                    s->properties.limits.maxViewports = 1;
+                    s->properties.limits.minInterpolationOffset = -0.5f;
+                    s->properties.limits.minMemoryMapAlignment = 4096;
+                    s->properties.limits.minStorageBufferOffsetAlignment = 256;
+                    s->properties.limits.minTexelBufferOffsetAlignment = 256;
+                    s->properties.limits.minTexelOffset = -8;
+                    s->properties.limits.minUniformBufferOffsetAlignment = 256;
+                    s->properties.limits.mipmapPrecisionBits = 4;
+                    s->properties.limits.pointSizeGranularity = 1;
+                    s->properties.limits.sampledImageColorSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.sampledImageDepthSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.sampledImageIntegerSampleCounts |= (VK_SAMPLE_COUNT_1_BIT);
+                    s->properties.limits.sampledImageStencilSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.standardSampleLocations = VK_TRUE;
+                    s->properties.limits.storageImageSampleCounts |= (VK_SAMPLE_COUNT_1_BIT);
+                    s->properties.limits.subPixelInterpolationOffsetBits = 4;
+                    s->properties.limits.subPixelPrecisionBits = 4;
+                    s->properties.limits.subTexelPrecisionBits = 4;
+                    s->properties.limits.viewportBoundsRange[0] = -8192;
+                    s->properties.limits.viewportBoundsRange[1] = 8191;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR: {
+                    VkPhysicalDeviceProperties2KHR* prettify_VkPhysicalDeviceProperties2KHR = static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.discreteQueuePriorities >= 2); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.discreteQueuePriorities >= 2), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.discreteQueuePriorities >= 2");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferColorSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferDepthSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferNoAttachmentsSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferNoAttachmentsSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferNoAttachmentsSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferStencilSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxBoundDescriptorSets >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxBoundDescriptorSets >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxBoundDescriptorSets >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxColorAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxColorAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxColorAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeSharedMemorySize >= 16384); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeSharedMemorySize >= 16384), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeSharedMemorySize >= 16384");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[0] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[0] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[0] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[1] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[1] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[1] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[2] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[2] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[2] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupInvocations >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupInvocations >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupInvocations >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[0] >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[0] >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[0] >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[1] >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[1] >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[1] >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[2] >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[2] >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[2] >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetInputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetInputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetInputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSampledImages >= 48); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSampledImages >= 48), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetSampledImages >= 48");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSamplers >= 48); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSamplers >= 48), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetSamplers >= 48");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffers >= 24); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffers >= 24), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageBuffers >= 24");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageImages >= 12); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageImages >= 12), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageImages >= 12");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffers >= 36); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffers >= 36), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetUniformBuffers >= 36");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndexedIndexValue >= 4294967295); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndexedIndexValue >= 4294967295), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDrawIndexedIndexValue >= 4294967295");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndirectCount >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndirectCount >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDrawIndirectCount >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentCombinedOutputResources >= 8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentCombinedOutputResources >= 8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentCombinedOutputResources >= 8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentInputComponents >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentInputComponents >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentInputComponents >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentOutputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentOutputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentOutputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferHeight >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferHeight >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferHeight >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferLayers >= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferLayers >= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferLayers >= 256");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferWidth >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferWidth >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferWidth >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageArrayLayers >= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageArrayLayers >= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageArrayLayers >= 256");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension1D >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension1D >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension1D >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension2D >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension2D >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension2D >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension3D >= 512); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension3D >= 512), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension3D >= 512");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimensionCube >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimensionCube >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimensionCube >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxInterpolationOffset >= 0.4375); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxInterpolationOffset >= 0.4375), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxInterpolationOffset >= 0.4375");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxMemoryAllocationCount >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxMemoryAllocationCount >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxMemoryAllocationCount >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorInputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorInputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorInputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSampledImages >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSampledImages >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorSampledImages >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSamplers >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSamplers >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorSamplers >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageBuffers >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageBuffers >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorStorageBuffers >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageImages >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageImages >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorStorageImages >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorUniformBuffers >= 12); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorUniformBuffers >= 12), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorUniformBuffers >= 12");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageResources >= 44); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageResources >= 44), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageResources >= 44");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPushConstantsSize >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPushConstantsSize >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPushConstantsSize >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSampleMaskWords >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSampleMaskWords >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSampleMaskWords >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAllocationCount >= 4000); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAllocationCount >= 4000), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerAllocationCount >= 4000");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAnisotropy >= 1.0); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAnisotropy >= 1.0), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerAnisotropy >= 1.0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerLodBias >= 2.0); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerLodBias >= 2.0), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerLodBias >= 2.0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxStorageBufferRange >= 134217728); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxStorageBufferRange >= 134217728), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxStorageBufferRange >= 134217728");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelBufferElements >= 65536); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelBufferElements >= 65536), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxTexelBufferElements >= 65536");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelOffset >= 7); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelOffset >= 7), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxTexelOffset >= 7");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxUniformBufferRange >= 16384); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxUniformBufferRange >= 16384), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxUniformBufferRange >= 16384");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributeOffset >= 2047); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributeOffset >= 2047), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputAttributeOffset >= 2047");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributes >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributes >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputAttributes >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindingStride >= 2048); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindingStride >= 2048), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputBindingStride >= 2048");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindings >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindings >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputBindings >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexOutputComponents >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexOutputComponents >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexOutputComponents >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[0] >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[0] >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewportDimensions[0] >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[1] >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[1] >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewportDimensions[1] >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewports >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewports >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewports >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minInterpolationOffset <= -0.5); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minInterpolationOffset <= -0.5), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minInterpolationOffset <= -0.5");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment <= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment <= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minMemoryMapAlignment <= 4096");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minStorageBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minTexelBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelOffset <= -8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelOffset <= -8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minTexelOffset <= -8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minUniformBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.mipmapPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.mipmapPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.mipmapPrecisionBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity <= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity <= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.pointSizeGranularity <= 1");
+                    ret = ret && (isMultiple(1, prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity)); VP_DEBUG_COND_MSG(!(isMultiple(1, prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity)), "Unsupported properties condition: isMultiple(1, prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageColorSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageDepthSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageIntegerSampleCounts, (VK_SAMPLE_COUNT_1_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageIntegerSampleCounts, (VK_SAMPLE_COUNT_1_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageIntegerSampleCounts contains (VK_SAMPLE_COUNT_1_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageStencilSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.standardSampleLocations == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.standardSampleLocations == VK_TRUE), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.standardSampleLocations == VK_TRUE");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.storageImageSampleCounts, (VK_SAMPLE_COUNT_1_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.storageImageSampleCounts, (VK_SAMPLE_COUNT_1_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.storageImageSampleCounts contains (VK_SAMPLE_COUNT_1_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelInterpolationOffsetBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelInterpolationOffsetBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subPixelInterpolationOffsetBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subPixelPrecisionBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subTexelPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subTexelPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subTexelPrecisionBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[0] <= -8192); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[0] <= -8192), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.viewportBoundsRange[0] <= -8192");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[1] >= 8191); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[1] >= 8191), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.viewportBoundsRange[1] >= 8191");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpFormatDesc formatDesc[] = {
+    {
+        VK_FORMAT_A1R5G5B5_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A1R5G5B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A1R5G5B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A2B10G10R10_UINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SRGB_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SRGB_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SRGB_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_UINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B10G11R11_UFLOAT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B4G4R4A4_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B4G4R4A4_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B4G4R4A4_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B8G8R8A8_SRGB,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_SRGB: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_SRGB: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B8G8R8A8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_D16_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_D16_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_D32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_D32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11G11_SNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11G11_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11_SNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R5G6B5_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R5G6B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R5G6B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SRGB,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SRGB: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SRGB: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(nullptr));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(nullptr));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+} //namespace baseline
+} // namespace VP_ANDROID_BASELINE_2021
+#endif // VP_ANDROID_baseline_2021
+
+#ifdef VP_ANDROID_baseline_2021_cpu_only
+namespace VP_ANDROID_BASELINE_2021_CPU_ONLY {
+
+static const VkStructureType featureStructTypes[] = {
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
+};
+
+static const VkStructureType propertyStructTypes[] = {
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
+};
+
+static const VkStructureType formatStructTypes[] = {
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR,
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR,
+};
+
+static const VkExtensionProperties instanceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SURFACE_EXTENSION_NAME, 1 },
+};
+
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_MAINTENANCE_1_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SWAPCHAIN_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* s = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    s->features.depthBiasClamp = VK_TRUE;
+                    s->features.fragmentStoresAndAtomics = VK_TRUE;
+                    s->features.fullDrawIndexUint32 = VK_TRUE;
+                    s->features.imageCubeArray = VK_TRUE;
+                    s->features.independentBlend = VK_TRUE;
+                    s->features.robustBufferAccess = VK_TRUE;
+                    s->features.sampleRateShading = VK_TRUE;
+                    s->features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderStorageImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE;
+                    s->features.textureCompressionASTC_LDR = VK_TRUE;
+                    s->features.textureCompressionETC2 = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* prettify_VkPhysicalDeviceFeatures2KHR = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.depthBiasClamp == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fragmentStoresAndAtomics == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fullDrawIndexUint32 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.imageCubeArray == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.independentBlend == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.robustBufferAccess == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.sampleRateShading == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderSampledImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionASTC_LDR == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionETC2 == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+        return ret;
+    }
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(nullptr));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(nullptr));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+
+namespace baseline {
+static const VkExtensionProperties instanceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SURFACE_EXTENSION_NAME, 1 },
+};
+
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_MAINTENANCE_1_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SWAPCHAIN_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* s = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    s->features.depthBiasClamp = VK_TRUE;
+                    s->features.fragmentStoresAndAtomics = VK_TRUE;
+                    s->features.fullDrawIndexUint32 = VK_TRUE;
+                    s->features.imageCubeArray = VK_TRUE;
+                    s->features.independentBlend = VK_TRUE;
+                    s->features.robustBufferAccess = VK_TRUE;
+                    s->features.sampleRateShading = VK_TRUE;
+                    s->features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderStorageImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE;
+                    s->features.textureCompressionASTC_LDR = VK_TRUE;
+                    s->features.textureCompressionETC2 = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* prettify_VkPhysicalDeviceFeatures2KHR = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.depthBiasClamp == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fragmentStoresAndAtomics == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fullDrawIndexUint32 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.imageCubeArray == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.independentBlend == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.robustBufferAccess == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.sampleRateShading == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderSampledImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionASTC_LDR == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionETC2 == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR: {
+                    VkPhysicalDeviceProperties2KHR* s = static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p));
+                    s->properties.limits.discreteQueuePriorities = 2;
+                    s->properties.limits.framebufferColorSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferDepthSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferNoAttachmentsSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferStencilSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.maxBoundDescriptorSets = 4;
+                    s->properties.limits.maxColorAttachments = 4;
+                    s->properties.limits.maxComputeSharedMemorySize = 16384;
+                    s->properties.limits.maxComputeWorkGroupCount[0] = 65535;
+                    s->properties.limits.maxComputeWorkGroupCount[1] = 65535;
+                    s->properties.limits.maxComputeWorkGroupCount[2] = 65535;
+                    s->properties.limits.maxComputeWorkGroupInvocations = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[0] = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[1] = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[2] = 64;
+                    s->properties.limits.maxDescriptorSetInputAttachments = 4;
+                    s->properties.limits.maxDescriptorSetSampledImages = 48;
+                    s->properties.limits.maxDescriptorSetSamplers = 48;
+                    s->properties.limits.maxDescriptorSetStorageBuffers = 24;
+                    s->properties.limits.maxDescriptorSetStorageBuffersDynamic = 4;
+                    s->properties.limits.maxDescriptorSetStorageImages = 12;
+                    s->properties.limits.maxDescriptorSetUniformBuffers = 36;
+                    s->properties.limits.maxDescriptorSetUniformBuffersDynamic = 8;
+                    s->properties.limits.maxDrawIndexedIndexValue = 4294967295;
+                    s->properties.limits.maxDrawIndirectCount = 1;
+                    s->properties.limits.maxFragmentCombinedOutputResources = 8;
+                    s->properties.limits.maxFragmentInputComponents = 64;
+                    s->properties.limits.maxFragmentOutputAttachments = 4;
+                    s->properties.limits.maxFramebufferHeight = 4096;
+                    s->properties.limits.maxFramebufferLayers = 256;
+                    s->properties.limits.maxFramebufferWidth = 4096;
+                    s->properties.limits.maxImageArrayLayers = 256;
+                    s->properties.limits.maxImageDimension1D = 4096;
+                    s->properties.limits.maxImageDimension2D = 4096;
+                    s->properties.limits.maxImageDimension3D = 512;
+                    s->properties.limits.maxImageDimensionCube = 4096;
+                    s->properties.limits.maxInterpolationOffset = 0.4375f;
+                    s->properties.limits.maxMemoryAllocationCount = 4096;
+                    s->properties.limits.maxPerStageDescriptorInputAttachments = 4;
+                    s->properties.limits.maxPerStageDescriptorSampledImages = 16;
+                    s->properties.limits.maxPerStageDescriptorSamplers = 16;
+                    s->properties.limits.maxPerStageDescriptorStorageBuffers = 4;
+                    s->properties.limits.maxPerStageDescriptorStorageImages = 4;
+                    s->properties.limits.maxPerStageDescriptorUniformBuffers = 12;
+                    s->properties.limits.maxPerStageResources = 44;
+                    s->properties.limits.maxPushConstantsSize = 128;
+                    s->properties.limits.maxSampleMaskWords = 1;
+                    s->properties.limits.maxSamplerAllocationCount = 4000;
+                    s->properties.limits.maxSamplerAnisotropy = 1.0f;
+                    s->properties.limits.maxSamplerLodBias = 2.0f;
+                    s->properties.limits.maxStorageBufferRange = 134217728;
+                    s->properties.limits.maxTexelBufferElements = 65536;
+                    s->properties.limits.maxTexelOffset = 7;
+                    s->properties.limits.maxUniformBufferRange = 16384;
+                    s->properties.limits.maxVertexInputAttributeOffset = 2047;
+                    s->properties.limits.maxVertexInputAttributes = 16;
+                    s->properties.limits.maxVertexInputBindingStride = 2048;
+                    s->properties.limits.maxVertexInputBindings = 16;
+                    s->properties.limits.maxVertexOutputComponents = 64;
+                    s->properties.limits.maxViewportDimensions[0] = 4096;
+                    s->properties.limits.maxViewportDimensions[1] = 4096;
+                    s->properties.limits.maxViewports = 1;
+                    s->properties.limits.minInterpolationOffset = -0.5f;
+                    s->properties.limits.minMemoryMapAlignment = 4096;
+                    s->properties.limits.minStorageBufferOffsetAlignment = 256;
+                    s->properties.limits.minTexelBufferOffsetAlignment = 256;
+                    s->properties.limits.minTexelOffset = -8;
+                    s->properties.limits.minUniformBufferOffsetAlignment = 256;
+                    s->properties.limits.mipmapPrecisionBits = 4;
+                    s->properties.limits.sampledImageColorSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.sampledImageDepthSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.sampledImageIntegerSampleCounts |= (VK_SAMPLE_COUNT_1_BIT);
+                    s->properties.limits.sampledImageStencilSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.standardSampleLocations = VK_TRUE;
+                    s->properties.limits.storageImageSampleCounts |= (VK_SAMPLE_COUNT_1_BIT);
+                    s->properties.limits.subPixelInterpolationOffsetBits = 4;
+                    s->properties.limits.subPixelPrecisionBits = 4;
+                    s->properties.limits.subTexelPrecisionBits = 4;
+                    s->properties.limits.viewportBoundsRange[0] = -8192;
+                    s->properties.limits.viewportBoundsRange[1] = 8191;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR: {
+                    VkPhysicalDeviceProperties2KHR* prettify_VkPhysicalDeviceProperties2KHR = static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.discreteQueuePriorities >= 2); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.discreteQueuePriorities >= 2), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.discreteQueuePriorities >= 2");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferColorSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferDepthSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferNoAttachmentsSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferNoAttachmentsSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferNoAttachmentsSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferStencilSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxBoundDescriptorSets >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxBoundDescriptorSets >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxBoundDescriptorSets >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxColorAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxColorAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxColorAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeSharedMemorySize >= 16384); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeSharedMemorySize >= 16384), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeSharedMemorySize >= 16384");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[0] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[0] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[0] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[1] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[1] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[1] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[2] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[2] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[2] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupInvocations >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupInvocations >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupInvocations >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[0] >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[0] >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[0] >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[1] >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[1] >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[1] >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[2] >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[2] >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[2] >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetInputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetInputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetInputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSampledImages >= 48); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSampledImages >= 48), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetSampledImages >= 48");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSamplers >= 48); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSamplers >= 48), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetSamplers >= 48");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffers >= 24); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffers >= 24), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageBuffers >= 24");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageImages >= 12); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageImages >= 12), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageImages >= 12");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffers >= 36); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffers >= 36), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetUniformBuffers >= 36");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndexedIndexValue >= 4294967295); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndexedIndexValue >= 4294967295), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDrawIndexedIndexValue >= 4294967295");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndirectCount >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndirectCount >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDrawIndirectCount >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentCombinedOutputResources >= 8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentCombinedOutputResources >= 8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentCombinedOutputResources >= 8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentInputComponents >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentInputComponents >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentInputComponents >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentOutputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentOutputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentOutputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferHeight >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferHeight >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferHeight >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferLayers >= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferLayers >= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferLayers >= 256");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferWidth >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferWidth >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferWidth >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageArrayLayers >= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageArrayLayers >= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageArrayLayers >= 256");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension1D >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension1D >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension1D >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension2D >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension2D >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension2D >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension3D >= 512); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension3D >= 512), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension3D >= 512");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimensionCube >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimensionCube >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimensionCube >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxInterpolationOffset >= 0.4375); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxInterpolationOffset >= 0.4375), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxInterpolationOffset >= 0.4375");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxMemoryAllocationCount >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxMemoryAllocationCount >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxMemoryAllocationCount >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorInputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorInputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorInputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSampledImages >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSampledImages >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorSampledImages >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSamplers >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSamplers >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorSamplers >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageBuffers >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageBuffers >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorStorageBuffers >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageImages >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageImages >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorStorageImages >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorUniformBuffers >= 12); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorUniformBuffers >= 12), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorUniformBuffers >= 12");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageResources >= 44); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageResources >= 44), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageResources >= 44");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPushConstantsSize >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPushConstantsSize >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPushConstantsSize >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSampleMaskWords >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSampleMaskWords >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSampleMaskWords >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAllocationCount >= 4000); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAllocationCount >= 4000), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerAllocationCount >= 4000");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAnisotropy >= 1.0); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAnisotropy >= 1.0), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerAnisotropy >= 1.0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerLodBias >= 2.0); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerLodBias >= 2.0), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerLodBias >= 2.0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxStorageBufferRange >= 134217728); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxStorageBufferRange >= 134217728), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxStorageBufferRange >= 134217728");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelBufferElements >= 65536); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelBufferElements >= 65536), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxTexelBufferElements >= 65536");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelOffset >= 7); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelOffset >= 7), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxTexelOffset >= 7");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxUniformBufferRange >= 16384); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxUniformBufferRange >= 16384), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxUniformBufferRange >= 16384");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributeOffset >= 2047); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributeOffset >= 2047), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputAttributeOffset >= 2047");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributes >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributes >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputAttributes >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindingStride >= 2048); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindingStride >= 2048), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputBindingStride >= 2048");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindings >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindings >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputBindings >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexOutputComponents >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexOutputComponents >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexOutputComponents >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[0] >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[0] >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewportDimensions[0] >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[1] >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[1] >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewportDimensions[1] >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewports >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewports >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewports >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minInterpolationOffset <= -0.5); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minInterpolationOffset <= -0.5), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minInterpolationOffset <= -0.5");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment <= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment <= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minMemoryMapAlignment <= 4096");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minStorageBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minTexelBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelOffset <= -8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelOffset <= -8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minTexelOffset <= -8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minUniformBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.mipmapPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.mipmapPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.mipmapPrecisionBits >= 4");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageColorSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageDepthSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageIntegerSampleCounts, (VK_SAMPLE_COUNT_1_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageIntegerSampleCounts, (VK_SAMPLE_COUNT_1_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageIntegerSampleCounts contains (VK_SAMPLE_COUNT_1_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageStencilSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.standardSampleLocations == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.standardSampleLocations == VK_TRUE), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.standardSampleLocations == VK_TRUE");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.storageImageSampleCounts, (VK_SAMPLE_COUNT_1_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.storageImageSampleCounts, (VK_SAMPLE_COUNT_1_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.storageImageSampleCounts contains (VK_SAMPLE_COUNT_1_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelInterpolationOffsetBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelInterpolationOffsetBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subPixelInterpolationOffsetBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subPixelPrecisionBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subTexelPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subTexelPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subTexelPrecisionBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[0] <= -8192); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[0] <= -8192), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.viewportBoundsRange[0] <= -8192");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[1] >= 8191); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[1] >= 8191), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.viewportBoundsRange[1] >= 8191");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpFormatDesc formatDesc[] = {
+    {
+        VK_FORMAT_A1R5G5B5_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A1R5G5B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A1R5G5B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A2B10G10R10_UINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SRGB_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SRGB_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SRGB_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_UINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B10G11R11_UFLOAT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B4G4R4A4_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B4G4R4A4_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B4G4R4A4_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B8G8R8A8_SRGB,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_SRGB: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_SRGB: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B8G8R8A8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_D16_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_D16_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_D32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_D32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11G11_SNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11G11_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11_SNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R5G6B5_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R5G6B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R5G6B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SRGB,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SRGB: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SRGB: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(nullptr));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(nullptr));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+} //namespace baseline
+} // namespace VP_ANDROID_BASELINE_2021_CPU_ONLY
+#endif // VP_ANDROID_baseline_2021_cpu_only
+
+#ifdef VP_ANDROID_baseline_2022
+namespace VP_ANDROID_BASELINE_2022 {
+
+static const VkStructureType featureStructTypes[] = {
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES,
+};
+
+static const VkStructureType propertyStructTypes[] = {
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES,
+};
+
+static const VkStructureType formatStructTypes[] = {
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR,
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR,
+};
+
+static const VkExtensionProperties instanceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SURFACE_EXTENSION_NAME, 1 },
+};
+
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_MAINTENANCE_1_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SWAPCHAIN_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_VARIABLE_POINTERS_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* s = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    s->features.depthBiasClamp = VK_TRUE;
+                    s->features.fragmentStoresAndAtomics = VK_TRUE;
+                    s->features.fullDrawIndexUint32 = VK_TRUE;
+                    s->features.imageCubeArray = VK_TRUE;
+                    s->features.independentBlend = VK_TRUE;
+                    s->features.largePoints = VK_TRUE;
+                    s->features.robustBufferAccess = VK_TRUE;
+                    s->features.sampleRateShading = VK_TRUE;
+                    s->features.shaderInt16 = VK_TRUE;
+                    s->features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderStorageBufferArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderStorageImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE;
+                    s->features.textureCompressionASTC_LDR = VK_TRUE;
+                    s->features.textureCompressionETC2 = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES: {
+                    VkPhysicalDeviceMultiviewFeatures* s = static_cast<VkPhysicalDeviceMultiviewFeatures*>(static_cast<void*>(p));
+                    s->multiview = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: {
+                    VkPhysicalDeviceSamplerYcbcrConversionFeatures* s = static_cast<VkPhysicalDeviceSamplerYcbcrConversionFeatures*>(static_cast<void*>(p));
+                    s->samplerYcbcrConversion = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES: {
+                    VkPhysicalDeviceShaderDrawParametersFeatures* s = static_cast<VkPhysicalDeviceShaderDrawParametersFeatures*>(static_cast<void*>(p));
+                    s->shaderDrawParameters = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES: {
+                    VkPhysicalDeviceVariablePointersFeatures* s = static_cast<VkPhysicalDeviceVariablePointersFeatures*>(static_cast<void*>(p));
+                    s->variablePointers = VK_TRUE;
+                    s->variablePointersStorageBuffer = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* prettify_VkPhysicalDeviceFeatures2KHR = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.depthBiasClamp == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fragmentStoresAndAtomics == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fullDrawIndexUint32 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.imageCubeArray == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.independentBlend == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.largePoints == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.largePoints == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.largePoints == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.robustBufferAccess == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.sampleRateShading == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderInt16 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderInt16 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderInt16 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderSampledImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageBufferArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageBufferArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageBufferArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionASTC_LDR == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionETC2 == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES: {
+                    VkPhysicalDeviceMultiviewFeatures* prettify_VkPhysicalDeviceMultiviewFeatures = static_cast<VkPhysicalDeviceMultiviewFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceMultiviewFeatures->multiview == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceMultiviewFeatures->multiview == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceMultiviewFeatures::multiview == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: {
+                    VkPhysicalDeviceSamplerYcbcrConversionFeatures* prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures = static_cast<VkPhysicalDeviceSamplerYcbcrConversionFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures->samplerYcbcrConversion == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures->samplerYcbcrConversion == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceSamplerYcbcrConversionFeatures::samplerYcbcrConversion == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES: {
+                    VkPhysicalDeviceShaderDrawParametersFeatures* prettify_VkPhysicalDeviceShaderDrawParametersFeatures = static_cast<VkPhysicalDeviceShaderDrawParametersFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceShaderDrawParametersFeatures->shaderDrawParameters == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceShaderDrawParametersFeatures->shaderDrawParameters == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceShaderDrawParametersFeatures::shaderDrawParameters == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES: {
+                    VkPhysicalDeviceVariablePointersFeatures* prettify_VkPhysicalDeviceVariablePointersFeatures = static_cast<VkPhysicalDeviceVariablePointersFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceVariablePointersFeatures->variablePointers == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceVariablePointersFeatures->variablePointers == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceVariablePointersFeatures::variablePointers == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceVariablePointersFeatures->variablePointersStorageBuffer == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceVariablePointersFeatures->variablePointersStorageBuffer == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceVariablePointersFeatures::variablePointersStorageBuffer == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+        return ret;
+    }
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceMultiviewFeatures physicalDeviceMultiviewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES, nullptr };
+        VkPhysicalDeviceSamplerYcbcrConversionFeatures physicalDeviceSamplerYcbcrConversionFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, &physicalDeviceMultiviewFeatures };
+        VkPhysicalDeviceShaderDrawParametersFeatures physicalDeviceShaderDrawParametersFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES, &physicalDeviceSamplerYcbcrConversionFeatures };
+        VkPhysicalDeviceVariablePointersFeatures physicalDeviceVariablePointersFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES, &physicalDeviceShaderDrawParametersFeatures };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceVariablePointersFeatures));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceMultiviewProperties physicalDeviceMultiviewProperties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceMultiviewProperties));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+
+namespace baseline {
+static const VkExtensionProperties instanceExtensions[] = {
+    VkExtensionProperties{ VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SURFACE_EXTENSION_NAME, 1 },
+};
+
+static const VkExtensionProperties deviceExtensions[] = {
+    VkExtensionProperties{ VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_MAINTENANCE_1_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_SWAPCHAIN_EXTENSION_NAME, 1 },
+    VkExtensionProperties{ VK_KHR_VARIABLE_POINTERS_EXTENSION_NAME, 1 },
+};
+
+static const VpFeatureDesc featureDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* s = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    s->features.depthBiasClamp = VK_TRUE;
+                    s->features.fragmentStoresAndAtomics = VK_TRUE;
+                    s->features.fullDrawIndexUint32 = VK_TRUE;
+                    s->features.imageCubeArray = VK_TRUE;
+                    s->features.independentBlend = VK_TRUE;
+                    s->features.largePoints = VK_TRUE;
+                    s->features.robustBufferAccess = VK_TRUE;
+                    s->features.sampleRateShading = VK_TRUE;
+                    s->features.shaderInt16 = VK_TRUE;
+                    s->features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderStorageBufferArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderStorageImageArrayDynamicIndexing = VK_TRUE;
+                    s->features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE;
+                    s->features.textureCompressionASTC_LDR = VK_TRUE;
+                    s->features.textureCompressionETC2 = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES: {
+                    VkPhysicalDeviceMultiviewFeatures* s = static_cast<VkPhysicalDeviceMultiviewFeatures*>(static_cast<void*>(p));
+                    s->multiview = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: {
+                    VkPhysicalDeviceSamplerYcbcrConversionFeatures* s = static_cast<VkPhysicalDeviceSamplerYcbcrConversionFeatures*>(static_cast<void*>(p));
+                    s->samplerYcbcrConversion = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES: {
+                    VkPhysicalDeviceShaderDrawParametersFeatures* s = static_cast<VkPhysicalDeviceShaderDrawParametersFeatures*>(static_cast<void*>(p));
+                    s->shaderDrawParameters = VK_TRUE;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES: {
+                    VkPhysicalDeviceVariablePointersFeatures* s = static_cast<VkPhysicalDeviceVariablePointersFeatures*>(static_cast<void*>(p));
+                    s->variablePointers = VK_TRUE;
+                    s->variablePointersStorageBuffer = VK_TRUE;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR: {
+                    VkPhysicalDeviceFeatures2KHR* prettify_VkPhysicalDeviceFeatures2KHR = static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.depthBiasClamp == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.depthBiasClamp == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fragmentStoresAndAtomics == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fragmentStoresAndAtomics == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.fullDrawIndexUint32 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.fullDrawIndexUint32 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.imageCubeArray == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.imageCubeArray == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.independentBlend == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.independentBlend == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.largePoints == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.largePoints == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.largePoints == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.robustBufferAccess == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.robustBufferAccess == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.sampleRateShading == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.sampleRateShading == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderInt16 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderInt16 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderInt16 == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderSampledImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderSampledImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageBufferArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageBufferArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageBufferArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderStorageImageArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderStorageImageArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.shaderUniformBufferArrayDynamicIndexing == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionASTC_LDR == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionASTC_LDR == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceFeatures2KHR->features.textureCompressionETC2 == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceFeatures2KHR::features.textureCompressionETC2 == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES: {
+                    VkPhysicalDeviceMultiviewFeatures* prettify_VkPhysicalDeviceMultiviewFeatures = static_cast<VkPhysicalDeviceMultiviewFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceMultiviewFeatures->multiview == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceMultiviewFeatures->multiview == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceMultiviewFeatures::multiview == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: {
+                    VkPhysicalDeviceSamplerYcbcrConversionFeatures* prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures = static_cast<VkPhysicalDeviceSamplerYcbcrConversionFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures->samplerYcbcrConversion == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceSamplerYcbcrConversionFeatures->samplerYcbcrConversion == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceSamplerYcbcrConversionFeatures::samplerYcbcrConversion == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES: {
+                    VkPhysicalDeviceShaderDrawParametersFeatures* prettify_VkPhysicalDeviceShaderDrawParametersFeatures = static_cast<VkPhysicalDeviceShaderDrawParametersFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceShaderDrawParametersFeatures->shaderDrawParameters == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceShaderDrawParametersFeatures->shaderDrawParameters == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceShaderDrawParametersFeatures::shaderDrawParameters == VK_TRUE");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES: {
+                    VkPhysicalDeviceVariablePointersFeatures* prettify_VkPhysicalDeviceVariablePointersFeatures = static_cast<VkPhysicalDeviceVariablePointersFeatures*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceVariablePointersFeatures->variablePointers == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceVariablePointersFeatures->variablePointers == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceVariablePointersFeatures::variablePointers == VK_TRUE");
+                    ret = ret && (prettify_VkPhysicalDeviceVariablePointersFeatures->variablePointersStorageBuffer == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceVariablePointersFeatures->variablePointersStorageBuffer == VK_TRUE), "Unsupported feature condition: VkPhysicalDeviceVariablePointersFeatures::variablePointersStorageBuffer == VK_TRUE");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpPropertyDesc propertyDesc = {
+    [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR: {
+                    VkPhysicalDeviceProperties2KHR* s = static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p));
+                    s->properties.limits.discreteQueuePriorities = 2;
+                    s->properties.limits.framebufferColorSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferDepthSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferNoAttachmentsSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.framebufferStencilSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.maxBoundDescriptorSets = 4;
+                    s->properties.limits.maxColorAttachments = 4;
+                    s->properties.limits.maxComputeSharedMemorySize = 16384;
+                    s->properties.limits.maxComputeWorkGroupCount[0] = 65535;
+                    s->properties.limits.maxComputeWorkGroupCount[1] = 65535;
+                    s->properties.limits.maxComputeWorkGroupCount[2] = 65535;
+                    s->properties.limits.maxComputeWorkGroupInvocations = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[0] = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[1] = 128;
+                    s->properties.limits.maxComputeWorkGroupSize[2] = 64;
+                    s->properties.limits.maxDescriptorSetInputAttachments = 4;
+                    s->properties.limits.maxDescriptorSetSampledImages = 48;
+                    s->properties.limits.maxDescriptorSetSamplers = 48;
+                    s->properties.limits.maxDescriptorSetStorageBuffers = 24;
+                    s->properties.limits.maxDescriptorSetStorageBuffersDynamic = 4;
+                    s->properties.limits.maxDescriptorSetStorageImages = 12;
+                    s->properties.limits.maxDescriptorSetUniformBuffers = 36;
+                    s->properties.limits.maxDescriptorSetUniformBuffersDynamic = 8;
+                    s->properties.limits.maxDrawIndexedIndexValue = 4294967295;
+                    s->properties.limits.maxDrawIndirectCount = 1;
+                    s->properties.limits.maxFragmentCombinedOutputResources = 8;
+                    s->properties.limits.maxFragmentInputComponents = 64;
+                    s->properties.limits.maxFragmentOutputAttachments = 4;
+                    s->properties.limits.maxFramebufferHeight = 4096;
+                    s->properties.limits.maxFramebufferLayers = 256;
+                    s->properties.limits.maxFramebufferWidth = 4096;
+                    s->properties.limits.maxImageArrayLayers = 256;
+                    s->properties.limits.maxImageDimension1D = 4096;
+                    s->properties.limits.maxImageDimension2D = 4096;
+                    s->properties.limits.maxImageDimension3D = 512;
+                    s->properties.limits.maxImageDimensionCube = 4096;
+                    s->properties.limits.maxInterpolationOffset = 0.4375f;
+                    s->properties.limits.maxMemoryAllocationCount = 4096;
+                    s->properties.limits.maxPerStageDescriptorInputAttachments = 4;
+                    s->properties.limits.maxPerStageDescriptorSampledImages = 16;
+                    s->properties.limits.maxPerStageDescriptorSamplers = 16;
+                    s->properties.limits.maxPerStageDescriptorStorageBuffers = 4;
+                    s->properties.limits.maxPerStageDescriptorStorageImages = 4;
+                    s->properties.limits.maxPerStageDescriptorUniformBuffers = 12;
+                    s->properties.limits.maxPerStageResources = 44;
+                    s->properties.limits.maxPushConstantsSize = 128;
+                    s->properties.limits.maxSampleMaskWords = 1;
+                    s->properties.limits.maxSamplerAllocationCount = 4000;
+                    s->properties.limits.maxSamplerAnisotropy = 1.0f;
+                    s->properties.limits.maxSamplerLodBias = 2.0f;
+                    s->properties.limits.maxStorageBufferRange = 134217728;
+                    s->properties.limits.maxTexelBufferElements = 65536;
+                    s->properties.limits.maxTexelOffset = 7;
+                    s->properties.limits.maxUniformBufferRange = 16384;
+                    s->properties.limits.maxVertexInputAttributeOffset = 2047;
+                    s->properties.limits.maxVertexInputAttributes = 16;
+                    s->properties.limits.maxVertexInputBindingStride = 2048;
+                    s->properties.limits.maxVertexInputBindings = 16;
+                    s->properties.limits.maxVertexOutputComponents = 64;
+                    s->properties.limits.maxViewportDimensions[0] = 4096;
+                    s->properties.limits.maxViewportDimensions[1] = 4096;
+                    s->properties.limits.maxViewports = 1;
+                    s->properties.limits.minInterpolationOffset = -0.5f;
+                    s->properties.limits.minMemoryMapAlignment = 4096;
+                    s->properties.limits.minStorageBufferOffsetAlignment = 256;
+                    s->properties.limits.minTexelBufferOffsetAlignment = 256;
+                    s->properties.limits.minTexelOffset = -8;
+                    s->properties.limits.minUniformBufferOffsetAlignment = 256;
+                    s->properties.limits.mipmapPrecisionBits = 4;
+                    s->properties.limits.pointSizeGranularity = 1;
+                    s->properties.limits.pointSizeRange[0] = 1.0f;
+                    s->properties.limits.pointSizeRange[1] = 511;
+                    s->properties.limits.sampledImageColorSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.sampledImageDepthSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.sampledImageIntegerSampleCounts |= (VK_SAMPLE_COUNT_1_BIT);
+                    s->properties.limits.sampledImageStencilSampleCounts |= (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT);
+                    s->properties.limits.standardSampleLocations = VK_TRUE;
+                    s->properties.limits.storageImageSampleCounts |= (VK_SAMPLE_COUNT_1_BIT);
+                    s->properties.limits.subPixelInterpolationOffsetBits = 4;
+                    s->properties.limits.subPixelPrecisionBits = 4;
+                    s->properties.limits.subTexelPrecisionBits = 4;
+                    s->properties.limits.viewportBoundsRange[0] = -8192;
+                    s->properties.limits.viewportBoundsRange[1] = 8191;
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES: {
+                    VkPhysicalDeviceMultiviewProperties* s = static_cast<VkPhysicalDeviceMultiviewProperties*>(static_cast<void*>(p));
+                    s->maxMultiviewInstanceIndex = 134217727;
+                    s->maxMultiviewViewCount = 6;
+                } break;
+                default: break;
+            }
+    },
+    [](VkBaseOutStructure* p) -> bool {
+        bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR: {
+                    VkPhysicalDeviceProperties2KHR* prettify_VkPhysicalDeviceProperties2KHR = static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.discreteQueuePriorities >= 2); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.discreteQueuePriorities >= 2), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.discreteQueuePriorities >= 2");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferColorSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferDepthSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferNoAttachmentsSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferNoAttachmentsSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferNoAttachmentsSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.framebufferStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.framebufferStencilSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxBoundDescriptorSets >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxBoundDescriptorSets >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxBoundDescriptorSets >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxColorAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxColorAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxColorAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeSharedMemorySize >= 16384); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeSharedMemorySize >= 16384), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeSharedMemorySize >= 16384");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[0] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[0] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[0] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[1] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[1] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[1] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[2] >= 65535); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupCount[2] >= 65535), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupCount[2] >= 65535");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupInvocations >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupInvocations >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupInvocations >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[0] >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[0] >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[0] >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[1] >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[1] >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[1] >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[2] >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxComputeWorkGroupSize[2] >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxComputeWorkGroupSize[2] >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetInputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetInputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetInputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSampledImages >= 48); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSampledImages >= 48), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetSampledImages >= 48");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSamplers >= 48); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetSamplers >= 48), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetSamplers >= 48");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffers >= 24); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffers >= 24), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageBuffers >= 24");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageBuffersDynamic >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageImages >= 12); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetStorageImages >= 12), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetStorageImages >= 12");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffers >= 36); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffers >= 36), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetUniformBuffers >= 36");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDescriptorSetUniformBuffersDynamic >= 8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndexedIndexValue >= 4294967295); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndexedIndexValue >= 4294967295), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDrawIndexedIndexValue >= 4294967295");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndirectCount >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxDrawIndirectCount >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxDrawIndirectCount >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentCombinedOutputResources >= 8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentCombinedOutputResources >= 8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentCombinedOutputResources >= 8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentInputComponents >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentInputComponents >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentInputComponents >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentOutputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFragmentOutputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFragmentOutputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferHeight >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferHeight >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferHeight >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferLayers >= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferLayers >= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferLayers >= 256");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferWidth >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxFramebufferWidth >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxFramebufferWidth >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageArrayLayers >= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageArrayLayers >= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageArrayLayers >= 256");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension1D >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension1D >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension1D >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension2D >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension2D >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension2D >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension3D >= 512); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimension3D >= 512), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimension3D >= 512");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimensionCube >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxImageDimensionCube >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxImageDimensionCube >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxInterpolationOffset >= 0.4375); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxInterpolationOffset >= 0.4375), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxInterpolationOffset >= 0.4375");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxMemoryAllocationCount >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxMemoryAllocationCount >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxMemoryAllocationCount >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorInputAttachments >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorInputAttachments >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorInputAttachments >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSampledImages >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSampledImages >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorSampledImages >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSamplers >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorSamplers >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorSamplers >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageBuffers >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageBuffers >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorStorageBuffers >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageImages >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorStorageImages >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorStorageImages >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorUniformBuffers >= 12); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageDescriptorUniformBuffers >= 12), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageDescriptorUniformBuffers >= 12");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageResources >= 44); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPerStageResources >= 44), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPerStageResources >= 44");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPushConstantsSize >= 128); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxPushConstantsSize >= 128), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxPushConstantsSize >= 128");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSampleMaskWords >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSampleMaskWords >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSampleMaskWords >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAllocationCount >= 4000); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAllocationCount >= 4000), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerAllocationCount >= 4000");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAnisotropy >= 1.0); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerAnisotropy >= 1.0), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerAnisotropy >= 1.0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerLodBias >= 2.0); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxSamplerLodBias >= 2.0), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxSamplerLodBias >= 2.0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxStorageBufferRange >= 134217728); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxStorageBufferRange >= 134217728), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxStorageBufferRange >= 134217728");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelBufferElements >= 65536); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelBufferElements >= 65536), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxTexelBufferElements >= 65536");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelOffset >= 7); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxTexelOffset >= 7), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxTexelOffset >= 7");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxUniformBufferRange >= 16384); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxUniformBufferRange >= 16384), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxUniformBufferRange >= 16384");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributeOffset >= 2047); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributeOffset >= 2047), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputAttributeOffset >= 2047");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributes >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputAttributes >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputAttributes >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindingStride >= 2048); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindingStride >= 2048), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputBindingStride >= 2048");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindings >= 16); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexInputBindings >= 16), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexInputBindings >= 16");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexOutputComponents >= 64); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxVertexOutputComponents >= 64), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxVertexOutputComponents >= 64");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[0] >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[0] >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewportDimensions[0] >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[1] >= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewportDimensions[1] >= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewportDimensions[1] >= 4096");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewports >= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.maxViewports >= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.maxViewports >= 1");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minInterpolationOffset <= -0.5); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minInterpolationOffset <= -0.5), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minInterpolationOffset <= -0.5");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment <= 4096); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment <= 4096), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minMemoryMapAlignment <= 4096");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minMemoryMapAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minStorageBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minStorageBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minTexelBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelOffset <= -8); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minTexelOffset <= -8), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minTexelOffset <= -8");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment <= 256); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment <= 256), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.minUniformBufferOffsetAlignment <= 256");
+                    ret = ret && ((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0); VP_DEBUG_COND_MSG(!((prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0), "Unsupported properties condition: (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment & (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.minUniformBufferOffsetAlignment - 1)) == 0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.mipmapPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.mipmapPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.mipmapPrecisionBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity <= 1); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity <= 1), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.pointSizeGranularity <= 1");
+                    ret = ret && (isMultiple(1, prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity)); VP_DEBUG_COND_MSG(!(isMultiple(1, prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity)), "Unsupported properties condition: isMultiple(1, prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeGranularity)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeRange[0] <= 1.0); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeRange[0] <= 1.0), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.pointSizeRange[0] <= 1.0");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeRange[1] >= 511); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.pointSizeRange[1] >= 511), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.pointSizeRange[1] >= 511");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageColorSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageColorSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageDepthSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageDepthSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageIntegerSampleCounts, (VK_SAMPLE_COUNT_1_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageIntegerSampleCounts, (VK_SAMPLE_COUNT_1_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageIntegerSampleCounts contains (VK_SAMPLE_COUNT_1_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.sampledImageStencilSampleCounts, (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.sampledImageStencilSampleCounts contains (VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.standardSampleLocations == VK_TRUE); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.standardSampleLocations == VK_TRUE), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.standardSampleLocations == VK_TRUE");
+                    ret = ret && (vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.storageImageSampleCounts, (VK_SAMPLE_COUNT_1_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.storageImageSampleCounts, (VK_SAMPLE_COUNT_1_BIT))), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.storageImageSampleCounts contains (VK_SAMPLE_COUNT_1_BIT)");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelInterpolationOffsetBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelInterpolationOffsetBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subPixelInterpolationOffsetBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subPixelPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subPixelPrecisionBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subTexelPrecisionBits >= 4); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.subTexelPrecisionBits >= 4), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.subTexelPrecisionBits >= 4");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[0] <= -8192); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[0] <= -8192), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.viewportBoundsRange[0] <= -8192");
+                    ret = ret && (prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[1] >= 8191); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceProperties2KHR->properties.limits.viewportBoundsRange[1] >= 8191), "Unsupported properties condition: VkPhysicalDeviceProperties2KHR::properties.limits.viewportBoundsRange[1] >= 8191");
+                } break;
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES: {
+                    VkPhysicalDeviceMultiviewProperties* prettify_VkPhysicalDeviceMultiviewProperties = static_cast<VkPhysicalDeviceMultiviewProperties*>(static_cast<void*>(p));
+                    ret = ret && (prettify_VkPhysicalDeviceMultiviewProperties->maxMultiviewInstanceIndex >= 134217727); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceMultiviewProperties->maxMultiviewInstanceIndex >= 134217727), "Unsupported properties condition: VkPhysicalDeviceMultiviewProperties::maxMultiviewInstanceIndex >= 134217727");
+                    ret = ret && (prettify_VkPhysicalDeviceMultiviewProperties->maxMultiviewViewCount >= 6); VP_DEBUG_COND_MSG(!(prettify_VkPhysicalDeviceMultiviewProperties->maxMultiviewViewCount >= 6), "Unsupported properties condition: VkPhysicalDeviceMultiviewProperties::maxMultiviewViewCount >= 6");
+                } break;
+                default: break;
+            }
+        return ret;
+    }
+};
+
+static const VpFormatDesc formatDesc[] = {
+    {
+        VK_FORMAT_A1R5G5B5_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A1R5G5B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A1R5G5B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A2B10G10R10_UINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A2B10G10R10_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_SRGB_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SRGB_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_SRGB_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_UINT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UINT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_A8B8G8R8_UNORM_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_10x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x10_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_12x12_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_4x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x4_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_5x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_6x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x5_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x6_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ASTC_8x8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B10G11R11_UFLOAT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B10G11R11_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B4G4R4A4_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B4G4R4A4_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B4G4R4A4_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B8G8R8A8_SRGB,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_SRGB: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_SRGB: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_B8G8R8A8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_B8G8R8A8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_D16_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_D16_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_D32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_D32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11G11_SNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11G11_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11G11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11_SNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_SNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_EAC_R11_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_EAC_R11_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16B16A16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16B16A16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16G16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16G16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R16_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R16_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R16_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32B32A32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32B32A32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32G32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32G32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_SFLOAT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SFLOAT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R32_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R32_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R5G6B5_UNORM_PACK16,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R5G6B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R5G6B5_UNORM_PACK16: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_SRGB,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SRGB: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_SRGB: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8B8A8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8B8A8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8G8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8G8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_SINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_SNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_SNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_UINT,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UINT: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+    {
+        VK_FORMAT_R8_UNORM,
+        [](VkBaseOutStructure* p) {
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* s = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    s->formatProperties.bufferFeatures |= (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
+                    s->formatProperties.linearTilingFeatures |= (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                    s->formatProperties.optimalTilingFeatures |= (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+                } break;
+                default: break;
+            }
+        },
+        [](VkBaseOutStructure* p) -> bool {
+            bool ret = true;
+            switch (p->sType) {
+                case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR: {
+                    VkFormatProperties2KHR* prettify_VkFormatProperties2KHR = static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p));
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.bufferFeatures, (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.bufferFeatures contains (VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.linearTilingFeatures, (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.linearTilingFeatures contains (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                    ret = ret && (vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))); VP_DEBUG_COND_MSG(!(vpCheckFlags(prettify_VkFormatProperties2KHR->formatProperties.optimalTilingFeatures, (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))), "Unsupported format condition for VK_FORMAT_R8_UNORM: VkFormatProperties2KHR::formatProperties.optimalTilingFeatures contains (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)");
+                } break;
+                default: break;
+            }
+            return ret;
+        }
+    },
+};
+
+static const VpStructChainerDesc chainerDesc = {
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceMultiviewFeatures physicalDeviceMultiviewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES, nullptr };
+        VkPhysicalDeviceSamplerYcbcrConversionFeatures physicalDeviceSamplerYcbcrConversionFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, &physicalDeviceMultiviewFeatures };
+        VkPhysicalDeviceShaderDrawParametersFeatures physicalDeviceShaderDrawParametersFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES, &physicalDeviceSamplerYcbcrConversionFeatures };
+        VkPhysicalDeviceVariablePointersFeatures physicalDeviceVariablePointersFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES, &physicalDeviceShaderDrawParametersFeatures };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceVariablePointersFeatures));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkPhysicalDeviceMultiviewProperties physicalDeviceMultiviewProperties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&physicalDeviceMultiviewProperties));
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        pfnCb(p, pUser);
+    },
+    [](VkBaseOutStructure* p, void* pUser, PFN_vpStructChainerCb pfnCb) {
+        VkFormatProperties3KHR formatProperties3KHR{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR, nullptr };
+        p->pNext = static_cast<VkBaseOutStructure*>(static_cast<void*>(&formatProperties3KHR));
+        pfnCb(p, pUser);
+    },
+};
+} //namespace baseline
+} // namespace VP_ANDROID_BASELINE_2022
+#endif // VP_ANDROID_baseline_2022
+
+
+#ifdef VP_ANDROID_15_minimums
+namespace VP_ANDROID_15_MINIMUMS {
+    namespace MUST {
+        static const VpVariantDesc variants[] = {
+            {
+                "MUST",
+                static_cast<uint32_t>(std::size(MUST::instanceExtensions)), MUST::instanceExtensions,
+                static_cast<uint32_t>(std::size(MUST::deviceExtensions)), MUST::deviceExtensions,
+                static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+                MUST::featureDesc,
+                static_cast<uint32_t>(std::size(propertyStructTypes)), propertyStructTypes,
+                MUST::propertyDesc,
+                0, nullptr,
+                0, nullptr,
+                static_cast<uint32_t>(std::size(formatStructTypes)), formatStructTypes,
+                static_cast<uint32_t>(std::size(MUST::formatDesc)), MUST::formatDesc,
+                MUST::chainerDesc,
+            },
+        };
+        static const uint32_t variantCount = static_cast<uint32_t>(std::size(variants));
+    } // namespace MUST
+
+    namespace primitivesGeneratedQuery_pipelineStatisticsQuery_ {
+        static const VpVariantDesc variants[] = {
+            {
+                "primitivesGeneratedQuery",
+                0, nullptr,
+                static_cast<uint32_t>(std::size(primitivesGeneratedQuery::deviceExtensions)), primitivesGeneratedQuery::deviceExtensions,
+                static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+                primitivesGeneratedQuery::featureDesc,
+                0, nullptr,
+                primitivesGeneratedQuery::propertyDesc,
+                0, nullptr,
+                0, nullptr,
+                0, nullptr,
+                0, nullptr,
+                primitivesGeneratedQuery::chainerDesc,
+            },
+            {
+                "pipelineStatisticsQuery",
+                0, nullptr,
+                0, nullptr,
+                static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+                pipelineStatisticsQuery::featureDesc,
+                0, nullptr,
+                pipelineStatisticsQuery::propertyDesc,
+                0, nullptr,
+                0, nullptr,
+                0, nullptr,
+                0, nullptr,
+                pipelineStatisticsQuery::chainerDesc,
+            },
+        };
+        static const uint32_t variantCount = static_cast<uint32_t>(std::size(variants));
+    } // namespace primitivesGeneratedQuery_pipelineStatisticsQuery_
+
+    namespace swBresenhamLines_hwBresenhamLines_ {
+        static const VpVariantDesc variants[] = {
+            {
+                "swBresenhamLines",
+                0, nullptr,
+                static_cast<uint32_t>(std::size(swBresenhamLines::deviceExtensions)), swBresenhamLines::deviceExtensions,
+                static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+                swBresenhamLines::featureDesc,
+                0, nullptr,
+                swBresenhamLines::propertyDesc,
+                0, nullptr,
+                0, nullptr,
+                0, nullptr,
+                0, nullptr,
+                swBresenhamLines::chainerDesc,
+            },
+            {
+                "hwBresenhamLines",
+                0, nullptr,
+                static_cast<uint32_t>(std::size(hwBresenhamLines::deviceExtensions)), hwBresenhamLines::deviceExtensions,
+                static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+                hwBresenhamLines::featureDesc,
+                0, nullptr,
+                hwBresenhamLines::propertyDesc,
+                0, nullptr,
+                0, nullptr,
+                0, nullptr,
+                0, nullptr,
+                hwBresenhamLines::chainerDesc,
+            },
+        };
+        static const uint32_t variantCount = static_cast<uint32_t>(std::size(variants));
+    } // namespace swBresenhamLines_hwBresenhamLines_
+
+    static const VpCapabilitiesDesc capabilities[] = {
+        MUST::variantCount, MUST::variants,
+        primitivesGeneratedQuery_pipelineStatisticsQuery_::variantCount, primitivesGeneratedQuery_pipelineStatisticsQuery_::variants,
+        swBresenhamLines_hwBresenhamLines_::variantCount, swBresenhamLines_hwBresenhamLines_::variants,
+    };
+    static const uint32_t capabilityCount = static_cast<uint32_t>(std::size(capabilities));
+
+    static const VpProfileProperties profiles[] = {
+        {VP_ANDROID_BASELINE_2022_NAME, VP_ANDROID_BASELINE_2022_SPEC_VERSION},
+    };
+    static const uint32_t profileCount = static_cast<uint32_t>(std::size(profiles));
+} // namespace VP_ANDROID_15_MINIMUMS
+#endif //VP_ANDROID_15_minimums
+
+#ifdef VP_ANDROID_baseline_2021
+namespace VP_ANDROID_BASELINE_2021 {
+    static const VpVariantDesc mergedCapabilities[] = {
+        "MERGED",
+        static_cast<uint32_t>(std::size(instanceExtensions)), instanceExtensions,
+        static_cast<uint32_t>(std::size(deviceExtensions)), deviceExtensions,
+        static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+            featureDesc,
+        0, nullptr,
+            propertyDesc,
+        0, nullptr,
+        0, nullptr,
+        0, nullptr,
+        0, nullptr,
+        chainerDesc,
+    };
+
+    namespace baseline {
+        static const VpVariantDesc variants[] = {
+            {
+                "baseline",
+                static_cast<uint32_t>(std::size(baseline::instanceExtensions)), baseline::instanceExtensions,
+                static_cast<uint32_t>(std::size(baseline::deviceExtensions)), baseline::deviceExtensions,
+                static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+                baseline::featureDesc,
+                static_cast<uint32_t>(std::size(propertyStructTypes)), propertyStructTypes,
+                baseline::propertyDesc,
+                0, nullptr,
+                0, nullptr,
+                static_cast<uint32_t>(std::size(formatStructTypes)), formatStructTypes,
+                static_cast<uint32_t>(std::size(baseline::formatDesc)), baseline::formatDesc,
+                baseline::chainerDesc,
+            },
+        };
+        static const uint32_t variantCount = static_cast<uint32_t>(std::size(variants));
+    } // namespace baseline
+
+    static const VpCapabilitiesDesc capabilities[] = {
+        baseline::variantCount, baseline::variants,
+    };
+    static const uint32_t capabilityCount = static_cast<uint32_t>(std::size(capabilities));
+} // namespace VP_ANDROID_BASELINE_2021
+#endif //VP_ANDROID_baseline_2021
+
+#ifdef VP_ANDROID_baseline_2021_cpu_only
+namespace VP_ANDROID_BASELINE_2021_CPU_ONLY {
+    static const VpVariantDesc mergedCapabilities[] = {
+        "MERGED",
+        static_cast<uint32_t>(std::size(instanceExtensions)), instanceExtensions,
+        static_cast<uint32_t>(std::size(deviceExtensions)), deviceExtensions,
+        static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+            featureDesc,
+        0, nullptr,
+            propertyDesc,
+        0, nullptr,
+        0, nullptr,
+        0, nullptr,
+        0, nullptr,
+        chainerDesc,
+    };
+
+    namespace baseline {
+        static const VpVariantDesc variants[] = {
+            {
+                "baseline",
+                static_cast<uint32_t>(std::size(baseline::instanceExtensions)), baseline::instanceExtensions,
+                static_cast<uint32_t>(std::size(baseline::deviceExtensions)), baseline::deviceExtensions,
+                static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+                baseline::featureDesc,
+                static_cast<uint32_t>(std::size(propertyStructTypes)), propertyStructTypes,
+                baseline::propertyDesc,
+                0, nullptr,
+                0, nullptr,
+                static_cast<uint32_t>(std::size(formatStructTypes)), formatStructTypes,
+                static_cast<uint32_t>(std::size(baseline::formatDesc)), baseline::formatDesc,
+                baseline::chainerDesc,
+            },
+        };
+        static const uint32_t variantCount = static_cast<uint32_t>(std::size(variants));
+    } // namespace baseline
+
+    static const VpCapabilitiesDesc capabilities[] = {
+        baseline::variantCount, baseline::variants,
+    };
+    static const uint32_t capabilityCount = static_cast<uint32_t>(std::size(capabilities));
+} // namespace VP_ANDROID_BASELINE_2021_CPU_ONLY
+#endif //VP_ANDROID_baseline_2021_cpu_only
+
+#ifdef VP_ANDROID_baseline_2022
+namespace VP_ANDROID_BASELINE_2022 {
+    static const VpVariantDesc mergedCapabilities[] = {
+        "MERGED",
+        static_cast<uint32_t>(std::size(instanceExtensions)), instanceExtensions,
+        static_cast<uint32_t>(std::size(deviceExtensions)), deviceExtensions,
+        static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+            featureDesc,
+        0, nullptr,
+            propertyDesc,
+        0, nullptr,
+        0, nullptr,
+        0, nullptr,
+        0, nullptr,
+        chainerDesc,
+    };
+
+    namespace baseline {
+        static const VpVariantDesc variants[] = {
+            {
+                "baseline",
+                static_cast<uint32_t>(std::size(baseline::instanceExtensions)), baseline::instanceExtensions,
+                static_cast<uint32_t>(std::size(baseline::deviceExtensions)), baseline::deviceExtensions,
+                static_cast<uint32_t>(std::size(featureStructTypes)), featureStructTypes,
+                baseline::featureDesc,
+                static_cast<uint32_t>(std::size(propertyStructTypes)), propertyStructTypes,
+                baseline::propertyDesc,
+                0, nullptr,
+                0, nullptr,
+                static_cast<uint32_t>(std::size(formatStructTypes)), formatStructTypes,
+                static_cast<uint32_t>(std::size(baseline::formatDesc)), baseline::formatDesc,
+                baseline::chainerDesc,
+            },
+        };
+        static const uint32_t variantCount = static_cast<uint32_t>(std::size(variants));
+    } // namespace baseline
+
+    static const VpCapabilitiesDesc capabilities[] = {
+        baseline::variantCount, baseline::variants,
+    };
+    static const uint32_t capabilityCount = static_cast<uint32_t>(std::size(capabilities));
+} // namespace VP_ANDROID_BASELINE_2022
+#endif //VP_ANDROID_baseline_2022
+
+static const VpProfileDesc profiles[] = {
+#ifdef VP_ANDROID_15_minimums
+    VpProfileDesc{
+        VpProfileProperties{ VP_ANDROID_15_MINIMUMS_NAME, VP_ANDROID_15_MINIMUMS_SPEC_VERSION },
+        VP_ANDROID_15_MINIMUMS_MIN_API_VERSION,
+        nullptr,
+        VP_ANDROID_15_MINIMUMS::profileCount, VP_ANDROID_15_MINIMUMS::profiles,
+        VP_ANDROID_15_MINIMUMS::capabilityCount, VP_ANDROID_15_MINIMUMS::capabilities,
+        0, nullptr,
+    },
+#endif // VP_ANDROID_15_MINIMUMS
+#ifdef VP_ANDROID_baseline_2021
+    VpProfileDesc{
+        VpProfileProperties{ VP_ANDROID_BASELINE_2021_NAME, VP_ANDROID_BASELINE_2021_SPEC_VERSION },
+        VP_ANDROID_BASELINE_2021_MIN_API_VERSION,
+        VP_ANDROID_BASELINE_2021::mergedCapabilities,
+        0, nullptr,
+        VP_ANDROID_BASELINE_2021::capabilityCount, VP_ANDROID_BASELINE_2021::capabilities,
+        0, nullptr,
+    },
+#endif // VP_ANDROID_BASELINE_2021
+#ifdef VP_ANDROID_baseline_2021_cpu_only
+    VpProfileDesc{
+        VpProfileProperties{ VP_ANDROID_BASELINE_2021_CPU_ONLY_NAME, VP_ANDROID_BASELINE_2021_CPU_ONLY_SPEC_VERSION },
+        VP_ANDROID_BASELINE_2021_CPU_ONLY_MIN_API_VERSION,
+        VP_ANDROID_BASELINE_2021_CPU_ONLY::mergedCapabilities,
+        0, nullptr,
+        VP_ANDROID_BASELINE_2021_CPU_ONLY::capabilityCount, VP_ANDROID_BASELINE_2021_CPU_ONLY::capabilities,
+        0, nullptr,
+    },
+#endif // VP_ANDROID_BASELINE_2021_CPU_ONLY
+#ifdef VP_ANDROID_baseline_2022
+    VpProfileDesc{
+        VpProfileProperties{ VP_ANDROID_BASELINE_2022_NAME, VP_ANDROID_BASELINE_2022_SPEC_VERSION },
+        VP_ANDROID_BASELINE_2022_MIN_API_VERSION,
+        VP_ANDROID_BASELINE_2022::mergedCapabilities,
+        0, nullptr,
+        VP_ANDROID_BASELINE_2022::capabilityCount, VP_ANDROID_BASELINE_2022::capabilities,
+        0, nullptr,
+    },
+#endif // VP_ANDROID_BASELINE_2022
+};
+static const uint32_t profileCount = static_cast<uint32_t>(std::size(profiles));
+
+
+struct FeaturesChain {
+    std::map<VkStructureType, std::size_t> structureSize;
+
+    template<typename T>
+    constexpr std::size_t size() const {
+        return (sizeof(T) - sizeof(VkBaseOutStructure)) / sizeof(VkBool32);
+    }
+
+	// Chain with all Vulkan Features structures
+    VkPhysicalDeviceDeviceGeneratedCommandsFeaturesNV physicalDeviceDeviceGeneratedCommandsFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_FEATURES_NV, nullptr };
+    VkPhysicalDeviceDeviceGeneratedCommandsComputeFeaturesNV physicalDeviceDeviceGeneratedCommandsComputeFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_COMPUTE_FEATURES_NV, nullptr };
+    VkPhysicalDevicePrivateDataFeatures physicalDevicePrivateDataFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIVATE_DATA_FEATURES, nullptr };
+    VkPhysicalDeviceVariablePointersFeatures physicalDeviceVariablePointersFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES, nullptr };
+    VkPhysicalDeviceMultiviewFeatures physicalDeviceMultiviewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES, nullptr };
+    VkPhysicalDevicePresentIdFeaturesKHR physicalDevicePresentIdFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR, nullptr };
+    VkPhysicalDevicePresentWaitFeaturesKHR physicalDevicePresentWaitFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR, nullptr };
+    VkPhysicalDevice16BitStorageFeatures physicalDevice16BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, nullptr };
+    VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures physicalDeviceShaderSubgroupExtendedTypesFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES, nullptr };
+    VkPhysicalDeviceSamplerYcbcrConversionFeatures physicalDeviceSamplerYcbcrConversionFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, nullptr };
+    VkPhysicalDeviceProtectedMemoryFeatures physicalDeviceProtectedMemoryFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES, nullptr };
+    VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT physicalDeviceBlendOperationAdvancedFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceMultiDrawFeaturesEXT physicalDeviceMultiDrawFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceInlineUniformBlockFeatures physicalDeviceInlineUniformBlockFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES, nullptr };
+    VkPhysicalDeviceMaintenance4Features physicalDeviceMaintenance4Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_FEATURES, nullptr };
+    VkPhysicalDeviceMaintenance5FeaturesKHR physicalDeviceMaintenance5FeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceMaintenance6FeaturesKHR physicalDeviceMaintenance6FeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_6_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceShaderDrawParametersFeatures physicalDeviceShaderDrawParametersFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES, nullptr };
+    VkPhysicalDeviceShaderFloat16Int8Features physicalDeviceShaderFloat16Int8Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, nullptr };
+    VkPhysicalDeviceHostQueryResetFeatures physicalDeviceHostQueryResetFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES, nullptr };
+    VkPhysicalDeviceGlobalPriorityQueryFeaturesKHR physicalDeviceGlobalPriorityQueryFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceDeviceMemoryReportFeaturesEXT physicalDeviceDeviceMemoryReportFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_MEMORY_REPORT_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDescriptorIndexingFeatures physicalDeviceDescriptorIndexingFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES, nullptr };
+    VkPhysicalDeviceTimelineSemaphoreFeatures physicalDeviceTimelineSemaphoreFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, nullptr };
+    VkPhysicalDevice8BitStorageFeatures physicalDevice8BitStorageFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, nullptr };
+    VkPhysicalDeviceConditionalRenderingFeaturesEXT physicalDeviceConditionalRenderingFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceVulkanMemoryModelFeatures physicalDeviceVulkanMemoryModelFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES, nullptr };
+    VkPhysicalDeviceShaderAtomicInt64Features physicalDeviceShaderAtomicInt64Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES, nullptr };
+    VkPhysicalDeviceShaderAtomicFloatFeaturesEXT physicalDeviceShaderAtomicFloatFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceShaderAtomicFloat2FeaturesEXT physicalDeviceShaderAtomicFloat2FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_2_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR physicalDeviceVertexAttributeDivisorFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceASTCDecodeFeaturesEXT physicalDeviceASTCDecodeFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ASTC_DECODE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceTransformFeedbackFeaturesEXT physicalDeviceTransformFeedbackFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceRepresentativeFragmentTestFeaturesNV physicalDeviceRepresentativeFragmentTestFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_REPRESENTATIVE_FRAGMENT_TEST_FEATURES_NV, nullptr };
+    VkPhysicalDeviceExclusiveScissorFeaturesNV physicalDeviceExclusiveScissorFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXCLUSIVE_SCISSOR_FEATURES_NV, nullptr };
+    VkPhysicalDeviceCornerSampledImageFeaturesNV physicalDeviceCornerSampledImageFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CORNER_SAMPLED_IMAGE_FEATURES_NV, nullptr };
+    VkPhysicalDeviceComputeShaderDerivativesFeaturesNV physicalDeviceComputeShaderDerivativesFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COMPUTE_SHADER_DERIVATIVES_FEATURES_NV, nullptr };
+    VkPhysicalDeviceShaderImageFootprintFeaturesNV physicalDeviceShaderImageFootprintFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_FOOTPRINT_FEATURES_NV, nullptr };
+    VkPhysicalDeviceDedicatedAllocationImageAliasingFeaturesNV physicalDeviceDedicatedAllocationImageAliasingFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEDICATED_ALLOCATION_IMAGE_ALIASING_FEATURES_NV, nullptr };
+    VkPhysicalDeviceCopyMemoryIndirectFeaturesNV physicalDeviceCopyMemoryIndirectFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COPY_MEMORY_INDIRECT_FEATURES_NV, nullptr };
+    VkPhysicalDeviceMemoryDecompressionFeaturesNV physicalDeviceMemoryDecompressionFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_DECOMPRESSION_FEATURES_NV, nullptr };
+    VkPhysicalDeviceShadingRateImageFeaturesNV physicalDeviceShadingRateImageFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADING_RATE_IMAGE_FEATURES_NV, nullptr };
+    VkPhysicalDeviceInvocationMaskFeaturesHUAWEI physicalDeviceInvocationMaskFeaturesHUAWEI{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INVOCATION_MASK_FEATURES_HUAWEI, nullptr };
+    VkPhysicalDeviceMeshShaderFeaturesNV physicalDeviceMeshShaderFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_NV, nullptr };
+    VkPhysicalDeviceMeshShaderFeaturesEXT physicalDeviceMeshShaderFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceAccelerationStructureFeaturesKHR physicalDeviceAccelerationStructureFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceRayTracingPipelineFeaturesKHR physicalDeviceRayTracingPipelineFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceRayQueryFeaturesKHR physicalDeviceRayQueryFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceRayTracingMaintenance1FeaturesKHR physicalDeviceRayTracingMaintenance1FeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MAINTENANCE_1_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceFragmentDensityMapFeaturesEXT physicalDeviceFragmentDensityMapFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceFragmentDensityMap2FeaturesEXT physicalDeviceFragmentDensityMap2FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_2_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceFragmentDensityMapOffsetFeaturesQCOM physicalDeviceFragmentDensityMapOffsetFeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_OFFSET_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceScalarBlockLayoutFeatures physicalDeviceScalarBlockLayoutFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES, nullptr };
+    VkPhysicalDeviceUniformBufferStandardLayoutFeatures physicalDeviceUniformBufferStandardLayoutFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES, nullptr };
+    VkPhysicalDeviceDepthClipEnableFeaturesEXT physicalDeviceDepthClipEnableFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceMemoryPriorityFeaturesEXT physicalDeviceMemoryPriorityFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT, nullptr };
+    VkPhysicalDevicePageableDeviceLocalMemoryFeaturesEXT physicalDevicePageableDeviceLocalMemoryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PAGEABLE_DEVICE_LOCAL_MEMORY_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceBufferDeviceAddressFeatures physicalDeviceBufferDeviceAddressFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, nullptr };
+    VkPhysicalDeviceBufferDeviceAddressFeaturesEXT physicalDeviceBufferDeviceAddressFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceImagelessFramebufferFeatures physicalDeviceImagelessFramebufferFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES, nullptr };
+    VkPhysicalDeviceTextureCompressionASTCHDRFeatures physicalDeviceTextureCompressionASTCHDRFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES, nullptr };
+    VkPhysicalDeviceCooperativeMatrixFeaturesNV physicalDeviceCooperativeMatrixFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_NV, nullptr };
+    VkPhysicalDeviceYcbcrImageArraysFeaturesEXT physicalDeviceYcbcrImageArraysFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_IMAGE_ARRAYS_FEATURES_EXT, nullptr };
+    VkPhysicalDevicePresentBarrierFeaturesNV physicalDevicePresentBarrierFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_BARRIER_FEATURES_NV, nullptr };
+    VkPhysicalDevicePerformanceQueryFeaturesKHR physicalDevicePerformanceQueryFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceCoverageReductionModeFeaturesNV physicalDeviceCoverageReductionModeFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV, nullptr };
+    VkPhysicalDeviceShaderIntegerFunctions2FeaturesINTEL physicalDeviceShaderIntegerFunctions2FeaturesINTEL{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_FUNCTIONS_2_FEATURES_INTEL, nullptr };
+    VkPhysicalDeviceShaderClockFeaturesKHR physicalDeviceShaderClockFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CLOCK_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceIndexTypeUint8FeaturesEXT physicalDeviceIndexTypeUint8FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceShaderSMBuiltinsFeaturesNV physicalDeviceShaderSMBuiltinsFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SM_BUILTINS_FEATURES_NV, nullptr };
+    VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT physicalDeviceFragmentShaderInterlockFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures physicalDeviceSeparateDepthStencilLayoutsFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES, nullptr };
+    VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT physicalDevicePrimitiveTopologyListRestartFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, nullptr };
+    VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR physicalDevicePipelineExecutablePropertiesFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceShaderDemoteToHelperInvocationFeatures physicalDeviceShaderDemoteToHelperInvocationFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES, nullptr };
+    VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT physicalDeviceTexelBufferAlignmentFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceSubgroupSizeControlFeatures physicalDeviceSubgroupSizeControlFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES, nullptr };
+    VkPhysicalDeviceLineRasterizationFeaturesEXT physicalDeviceLineRasterizationFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, nullptr };
+    VkPhysicalDevicePipelineCreationCacheControlFeatures physicalDevicePipelineCreationCacheControlFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES, nullptr };
+    VkPhysicalDeviceVulkan11Features physicalDeviceVulkan11Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, nullptr };
+    VkPhysicalDeviceVulkan12Features physicalDeviceVulkan12Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, nullptr };
+    VkPhysicalDeviceVulkan13Features physicalDeviceVulkan13Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, nullptr };
+    VkPhysicalDeviceCoherentMemoryFeaturesAMD physicalDeviceCoherentMemoryFeaturesAMD{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COHERENT_MEMORY_FEATURES_AMD, nullptr };
+    VkPhysicalDeviceCustomBorderColorFeaturesEXT physicalDeviceCustomBorderColorFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceBorderColorSwizzleFeaturesEXT physicalDeviceBorderColorSwizzleFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BORDER_COLOR_SWIZZLE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceExtendedDynamicStateFeaturesEXT physicalDeviceExtendedDynamicStateFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceExtendedDynamicState2FeaturesEXT physicalDeviceExtendedDynamicState2FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceExtendedDynamicState3FeaturesEXT physicalDeviceExtendedDynamicState3FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDiagnosticsConfigFeaturesNV physicalDeviceDiagnosticsConfigFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DIAGNOSTICS_CONFIG_FEATURES_NV, nullptr };
+    VkPhysicalDeviceZeroInitializeWorkgroupMemoryFeatures physicalDeviceZeroInitializeWorkgroupMemoryFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ZERO_INITIALIZE_WORKGROUP_MEMORY_FEATURES, nullptr };
+    VkPhysicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR physicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceRobustness2FeaturesEXT physicalDeviceRobustness2FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceImageRobustnessFeatures physicalDeviceImageRobustnessFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES, nullptr };
+    VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR physicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_FEATURES_KHR, nullptr };
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VkPhysicalDevicePortabilitySubsetFeaturesKHR physicalDevicePortabilitySubsetFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR, nullptr };
+#endif
+    VkPhysicalDevice4444FormatsFeaturesEXT physicalDevice4444FormatsFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceSubpassShadingFeaturesHUAWEI physicalDeviceSubpassShadingFeaturesHUAWEI{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBPASS_SHADING_FEATURES_HUAWEI, nullptr };
+    VkPhysicalDeviceClusterCullingShaderFeaturesHUAWEI physicalDeviceClusterCullingShaderFeaturesHUAWEI{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CLUSTER_CULLING_SHADER_FEATURES_HUAWEI, nullptr };
+    VkPhysicalDeviceShaderImageAtomicInt64FeaturesEXT physicalDeviceShaderImageAtomicInt64FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_ATOMIC_INT64_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceFragmentShadingRateFeaturesKHR physicalDeviceFragmentShadingRateFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceShaderTerminateInvocationFeatures physicalDeviceShaderTerminateInvocationFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TERMINATE_INVOCATION_FEATURES, nullptr };
+    VkPhysicalDeviceFragmentShadingRateEnumsFeaturesNV physicalDeviceFragmentShadingRateEnumsFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_ENUMS_FEATURES_NV, nullptr };
+    VkPhysicalDeviceImage2DViewOf3DFeaturesEXT physicalDeviceImage2DViewOf3DFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_2D_VIEW_OF_3D_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceImageSlicedViewOf3DFeaturesEXT physicalDeviceImageSlicedViewOf3DFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_SLICED_VIEW_OF_3D_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT physicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceMutableDescriptorTypeFeaturesEXT physicalDeviceMutableDescriptorTypeFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MUTABLE_DESCRIPTOR_TYPE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDepthClipControlFeaturesEXT physicalDeviceDepthClipControlFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT physicalDeviceVertexInputDynamicStateFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceExternalMemoryRDMAFeaturesNV physicalDeviceExternalMemoryRDMAFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_RDMA_FEATURES_NV, nullptr };
+    VkPhysicalDeviceColorWriteEnableFeaturesEXT physicalDeviceColorWriteEnableFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceSynchronization2Features physicalDeviceSynchronization2Features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES, nullptr };
+    VkPhysicalDeviceHostImageCopyFeaturesEXT physicalDeviceHostImageCopyFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES_EXT, nullptr };
+    VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT physicalDevicePrimitivesGeneratedQueryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceLegacyDitheringFeaturesEXT physicalDeviceLegacyDitheringFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LEGACY_DITHERING_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceMultisampledRenderToSingleSampledFeaturesEXT physicalDeviceMultisampledRenderToSingleSampledFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_FEATURES_EXT, nullptr };
+    VkPhysicalDevicePipelineProtectedAccessFeaturesEXT physicalDevicePipelineProtectedAccessFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_PROTECTED_ACCESS_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceVideoMaintenance1FeaturesKHR physicalDeviceVideoMaintenance1FeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_MAINTENANCE_1_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceInheritedViewportScissorFeaturesNV physicalDeviceInheritedViewportScissorFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INHERITED_VIEWPORT_SCISSOR_FEATURES_NV, nullptr };
+    VkPhysicalDeviceYcbcr2Plane444FormatsFeaturesEXT physicalDeviceYcbcr2Plane444FormatsFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_2_PLANE_444_FORMATS_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceProvokingVertexFeaturesEXT physicalDeviceProvokingVertexFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDescriptorBufferFeaturesEXT physicalDeviceDescriptorBufferFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceShaderIntegerDotProductFeatures physicalDeviceShaderIntegerDotProductFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_FEATURES, nullptr };
+    VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR physicalDeviceFragmentShaderBarycentricFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceRayTracingMotionBlurFeaturesNV physicalDeviceRayTracingMotionBlurFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MOTION_BLUR_FEATURES_NV, nullptr };
+    VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT physicalDeviceRGBA10X6FormatsFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RGBA10X6_FORMATS_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDynamicRenderingFeatures physicalDeviceDynamicRenderingFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, nullptr };
+    VkPhysicalDeviceImageViewMinLodFeaturesEXT physicalDeviceImageViewMinLodFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_VIEW_MIN_LOD_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT physicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceLinearColorAttachmentFeaturesNV physicalDeviceLinearColorAttachmentFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINEAR_COLOR_ATTACHMENT_FEATURES_NV, nullptr };
+    VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT physicalDeviceGraphicsPipelineLibraryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDescriptorSetHostMappingFeaturesVALVE physicalDeviceDescriptorSetHostMappingFeaturesVALVE{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_SET_HOST_MAPPING_FEATURES_VALVE, nullptr };
+    VkPhysicalDeviceNestedCommandBufferFeaturesEXT physicalDeviceNestedCommandBufferFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NESTED_COMMAND_BUFFER_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceShaderModuleIdentifierFeaturesEXT physicalDeviceShaderModuleIdentifierFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_MODULE_IDENTIFIER_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceImageCompressionControlFeaturesEXT physicalDeviceImageCompressionControlFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT physicalDeviceImageCompressionControlSwapchainFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceSubpassMergeFeedbackFeaturesEXT physicalDeviceSubpassMergeFeedbackFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBPASS_MERGE_FEEDBACK_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceOpacityMicromapFeaturesEXT physicalDeviceOpacityMicromapFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_OPACITY_MICROMAP_FEATURES_EXT, nullptr };
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VkPhysicalDeviceDisplacementMicromapFeaturesNV physicalDeviceDisplacementMicromapFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DISPLACEMENT_MICROMAP_FEATURES_NV, nullptr };
+#endif
+    VkPhysicalDevicePipelinePropertiesFeaturesEXT physicalDevicePipelinePropertiesFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_PROPERTIES_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceShaderEarlyAndLateFragmentTestsFeaturesAMD physicalDeviceShaderEarlyAndLateFragmentTestsFeaturesAMD{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_EARLY_AND_LATE_FRAGMENT_TESTS_FEATURES_AMD, nullptr };
+    VkPhysicalDeviceNonSeamlessCubeMapFeaturesEXT physicalDeviceNonSeamlessCubeMapFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NON_SEAMLESS_CUBE_MAP_FEATURES_EXT, nullptr };
+    VkPhysicalDevicePipelineRobustnessFeaturesEXT physicalDevicePipelineRobustnessFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceImageProcessingFeaturesQCOM physicalDeviceImageProcessingFeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_PROCESSING_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceTilePropertiesFeaturesQCOM physicalDeviceTilePropertiesFeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TILE_PROPERTIES_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceAmigoProfilingFeaturesSEC physicalDeviceAmigoProfilingFeaturesSEC{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_AMIGO_PROFILING_FEATURES_SEC, nullptr };
+    VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT physicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDepthClampZeroOneFeaturesEXT physicalDeviceDepthClampZeroOneFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLAMP_ZERO_ONE_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceAddressBindingReportFeaturesEXT physicalDeviceAddressBindingReportFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ADDRESS_BINDING_REPORT_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceOpticalFlowFeaturesNV physicalDeviceOpticalFlowFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_OPTICAL_FLOW_FEATURES_NV, nullptr };
+    VkPhysicalDeviceFaultFeaturesEXT physicalDeviceFaultFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT, nullptr };
+    VkPhysicalDevicePipelineLibraryGroupHandlesFeaturesEXT physicalDevicePipelineLibraryGroupHandlesFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_LIBRARY_GROUP_HANDLES_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceShaderCoreBuiltinsFeaturesARM physicalDeviceShaderCoreBuiltinsFeaturesARM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CORE_BUILTINS_FEATURES_ARM, nullptr };
+    VkPhysicalDeviceFrameBoundaryFeaturesEXT physicalDeviceFrameBoundaryFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAME_BOUNDARY_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT physicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT physicalDeviceSwapchainMaintenance1FeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceDepthBiasControlFeaturesEXT physicalDeviceDepthBiasControlFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_BIAS_CONTROL_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceRayTracingInvocationReorderFeaturesNV physicalDeviceRayTracingInvocationReorderFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_INVOCATION_REORDER_FEATURES_NV, nullptr };
+    VkPhysicalDeviceExtendedSparseAddressSpaceFeaturesNV physicalDeviceExtendedSparseAddressSpaceFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_SPARSE_ADDRESS_SPACE_FEATURES_NV, nullptr };
+    VkPhysicalDeviceMultiviewPerViewViewportsFeaturesQCOM physicalDeviceMultiviewPerViewViewportsFeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_VIEWPORTS_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceRayTracingPositionFetchFeaturesKHR physicalDeviceRayTracingPositionFetchFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_POSITION_FETCH_FEATURES_KHR, nullptr };
+    VkPhysicalDeviceMultiviewPerViewRenderAreasFeaturesQCOM physicalDeviceMultiviewPerViewRenderAreasFeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_RENDER_AREAS_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceShaderObjectFeaturesEXT physicalDeviceShaderObjectFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT, nullptr };
+    VkPhysicalDeviceShaderTileImageFeaturesEXT physicalDeviceShaderTileImageFeaturesEXT{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TILE_IMAGE_FEATURES_EXT, nullptr };
+#ifdef VK_USE_PLATFORM_SCREEN_QNX
+    VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX physicalDeviceExternalMemoryScreenBufferFeaturesQNX{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_SCREEN_BUFFER_FEATURES_QNX, nullptr };
+#endif
+    VkPhysicalDeviceCooperativeMatrixFeaturesKHR physicalDeviceCooperativeMatrixFeaturesKHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR, nullptr };
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VkPhysicalDeviceShaderEnqueueFeaturesAMDX physicalDeviceShaderEnqueueFeaturesAMDX{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ENQUEUE_FEATURES_AMDX, nullptr };
+#endif
+    VkPhysicalDeviceCubicClampFeaturesQCOM physicalDeviceCubicClampFeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUBIC_CLAMP_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceYcbcrDegammaFeaturesQCOM physicalDeviceYcbcrDegammaFeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_DEGAMMA_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceCubicWeightsFeaturesQCOM physicalDeviceCubicWeightsFeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUBIC_WEIGHTS_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceImageProcessing2FeaturesQCOM physicalDeviceImageProcessing2FeaturesQCOM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_PROCESSING_2_FEATURES_QCOM, nullptr };
+    VkPhysicalDeviceDescriptorPoolOverallocationFeaturesNV physicalDeviceDescriptorPoolOverallocationFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_POOL_OVERALLOCATION_FEATURES_NV, nullptr };
+    VkPhysicalDevicePerStageDescriptorSetFeaturesNV physicalDevicePerStageDescriptorSetFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PER_STAGE_DESCRIPTOR_SET_FEATURES_NV, nullptr };
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+    VkPhysicalDeviceExternalFormatResolveFeaturesANDROID physicalDeviceExternalFormatResolveFeaturesANDROID{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FORMAT_RESOLVE_FEATURES_ANDROID, nullptr };
+#endif
+    VkPhysicalDeviceCudaKernelLaunchFeaturesNV physicalDeviceCudaKernelLaunchFeaturesNV{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUDA_KERNEL_LAUNCH_FEATURES_NV, nullptr };
+    VkPhysicalDeviceSchedulingControlsFeaturesARM physicalDeviceSchedulingControlsFeaturesARM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCHEDULING_CONTROLS_FEATURES_ARM, nullptr };
+    VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG physicalDeviceRelaxedLineRasterizationFeaturesIMG{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG, nullptr };
+    VkPhysicalDeviceRenderPassStripedFeaturesARM physicalDeviceRenderPassStripedFeaturesARM{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RENDER_PASS_STRIPED_FEATURES_ARM, nullptr };
+    VkPhysicalDeviceFeatures2KHR physicalDeviceFeatures2KHR{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR, nullptr };
+
+    FeaturesChain() {
+        // Initializing all feature structures, number of Features (VkBool32) per structure.
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_FEATURES_NV, size<VkPhysicalDeviceDeviceGeneratedCommandsFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_COMPUTE_FEATURES_NV, size<VkPhysicalDeviceDeviceGeneratedCommandsComputeFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIVATE_DATA_FEATURES, size<VkPhysicalDevicePrivateDataFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES, size<VkPhysicalDeviceVariablePointersFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES, size<VkPhysicalDeviceMultiviewFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR, size<VkPhysicalDevicePresentIdFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR, size<VkPhysicalDevicePresentWaitFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, size<VkPhysicalDevice16BitStorageFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES, size<VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, size<VkPhysicalDeviceSamplerYcbcrConversionFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES, size<VkPhysicalDeviceProtectedMemoryFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT, size<VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT, size<VkPhysicalDeviceMultiDrawFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES, size<VkPhysicalDeviceInlineUniformBlockFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_FEATURES, size<VkPhysicalDeviceMaintenance4Features>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES_KHR, size<VkPhysicalDeviceMaintenance5FeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_6_FEATURES_KHR, size<VkPhysicalDeviceMaintenance6FeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES, size<VkPhysicalDeviceShaderDrawParametersFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, size<VkPhysicalDeviceShaderFloat16Int8Features>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES, size<VkPhysicalDeviceHostQueryResetFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_KHR, size<VkPhysicalDeviceGlobalPriorityQueryFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_MEMORY_REPORT_FEATURES_EXT, size<VkPhysicalDeviceDeviceMemoryReportFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES, size<VkPhysicalDeviceDescriptorIndexingFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, size<VkPhysicalDeviceTimelineSemaphoreFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, size<VkPhysicalDevice8BitStorageFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT, size<VkPhysicalDeviceConditionalRenderingFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES, size<VkPhysicalDeviceVulkanMemoryModelFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES, size<VkPhysicalDeviceShaderAtomicInt64Features>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT, size<VkPhysicalDeviceShaderAtomicFloatFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_2_FEATURES_EXT, size<VkPhysicalDeviceShaderAtomicFloat2FeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR, size<VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ASTC_DECODE_FEATURES_EXT, size<VkPhysicalDeviceASTCDecodeFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT, size<VkPhysicalDeviceTransformFeedbackFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_REPRESENTATIVE_FRAGMENT_TEST_FEATURES_NV, size<VkPhysicalDeviceRepresentativeFragmentTestFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXCLUSIVE_SCISSOR_FEATURES_NV, size<VkPhysicalDeviceExclusiveScissorFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CORNER_SAMPLED_IMAGE_FEATURES_NV, size<VkPhysicalDeviceCornerSampledImageFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COMPUTE_SHADER_DERIVATIVES_FEATURES_NV, size<VkPhysicalDeviceComputeShaderDerivativesFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_FOOTPRINT_FEATURES_NV, size<VkPhysicalDeviceShaderImageFootprintFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEDICATED_ALLOCATION_IMAGE_ALIASING_FEATURES_NV, size<VkPhysicalDeviceDedicatedAllocationImageAliasingFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COPY_MEMORY_INDIRECT_FEATURES_NV, size<VkPhysicalDeviceCopyMemoryIndirectFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_DECOMPRESSION_FEATURES_NV, size<VkPhysicalDeviceMemoryDecompressionFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADING_RATE_IMAGE_FEATURES_NV, size<VkPhysicalDeviceShadingRateImageFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INVOCATION_MASK_FEATURES_HUAWEI, size<VkPhysicalDeviceInvocationMaskFeaturesHUAWEI>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_NV, size<VkPhysicalDeviceMeshShaderFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT, size<VkPhysicalDeviceMeshShaderFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR, size<VkPhysicalDeviceAccelerationStructureFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR, size<VkPhysicalDeviceRayTracingPipelineFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR, size<VkPhysicalDeviceRayQueryFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MAINTENANCE_1_FEATURES_KHR, size<VkPhysicalDeviceRayTracingMaintenance1FeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT, size<VkPhysicalDeviceFragmentDensityMapFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_2_FEATURES_EXT, size<VkPhysicalDeviceFragmentDensityMap2FeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_OFFSET_FEATURES_QCOM, size<VkPhysicalDeviceFragmentDensityMapOffsetFeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES, size<VkPhysicalDeviceScalarBlockLayoutFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES, size<VkPhysicalDeviceUniformBufferStandardLayoutFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT, size<VkPhysicalDeviceDepthClipEnableFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT, size<VkPhysicalDeviceMemoryPriorityFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PAGEABLE_DEVICE_LOCAL_MEMORY_FEATURES_EXT, size<VkPhysicalDevicePageableDeviceLocalMemoryFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, size<VkPhysicalDeviceBufferDeviceAddressFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT, size<VkPhysicalDeviceBufferDeviceAddressFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES, size<VkPhysicalDeviceImagelessFramebufferFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES, size<VkPhysicalDeviceTextureCompressionASTCHDRFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_NV, size<VkPhysicalDeviceCooperativeMatrixFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_IMAGE_ARRAYS_FEATURES_EXT, size<VkPhysicalDeviceYcbcrImageArraysFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_BARRIER_FEATURES_NV, size<VkPhysicalDevicePresentBarrierFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR, size<VkPhysicalDevicePerformanceQueryFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV, size<VkPhysicalDeviceCoverageReductionModeFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_FUNCTIONS_2_FEATURES_INTEL, size<VkPhysicalDeviceShaderIntegerFunctions2FeaturesINTEL>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CLOCK_FEATURES_KHR, size<VkPhysicalDeviceShaderClockFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT, size<VkPhysicalDeviceIndexTypeUint8FeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SM_BUILTINS_FEATURES_NV, size<VkPhysicalDeviceShaderSMBuiltinsFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT, size<VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES, size<VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, size<VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR, size<VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES, size<VkPhysicalDeviceShaderDemoteToHelperInvocationFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT, size<VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES, size<VkPhysicalDeviceSubgroupSizeControlFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, size<VkPhysicalDeviceLineRasterizationFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES, size<VkPhysicalDevicePipelineCreationCacheControlFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, size<VkPhysicalDeviceVulkan11Features>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, size<VkPhysicalDeviceVulkan12Features>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, size<VkPhysicalDeviceVulkan13Features>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COHERENT_MEMORY_FEATURES_AMD, size<VkPhysicalDeviceCoherentMemoryFeaturesAMD>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT, size<VkPhysicalDeviceCustomBorderColorFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BORDER_COLOR_SWIZZLE_FEATURES_EXT, size<VkPhysicalDeviceBorderColorSwizzleFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, size<VkPhysicalDeviceExtendedDynamicStateFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT, size<VkPhysicalDeviceExtendedDynamicState2FeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT, size<VkPhysicalDeviceExtendedDynamicState3FeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DIAGNOSTICS_CONFIG_FEATURES_NV, size<VkPhysicalDeviceDiagnosticsConfigFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ZERO_INITIALIZE_WORKGROUP_MEMORY_FEATURES, size<VkPhysicalDeviceZeroInitializeWorkgroupMemoryFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_FEATURES_KHR, size<VkPhysicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, size<VkPhysicalDeviceRobustness2FeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES, size<VkPhysicalDeviceImageRobustnessFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_FEATURES_KHR, size<VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR>() });
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR, size<VkPhysicalDevicePortabilitySubsetFeaturesKHR>() });
+#endif
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT, size<VkPhysicalDevice4444FormatsFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBPASS_SHADING_FEATURES_HUAWEI, size<VkPhysicalDeviceSubpassShadingFeaturesHUAWEI>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CLUSTER_CULLING_SHADER_FEATURES_HUAWEI, size<VkPhysicalDeviceClusterCullingShaderFeaturesHUAWEI>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_ATOMIC_INT64_FEATURES_EXT, size<VkPhysicalDeviceShaderImageAtomicInt64FeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR, size<VkPhysicalDeviceFragmentShadingRateFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TERMINATE_INVOCATION_FEATURES, size<VkPhysicalDeviceShaderTerminateInvocationFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_ENUMS_FEATURES_NV, size<VkPhysicalDeviceFragmentShadingRateEnumsFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_2D_VIEW_OF_3D_FEATURES_EXT, size<VkPhysicalDeviceImage2DViewOf3DFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_SLICED_VIEW_OF_3D_FEATURES_EXT, size<VkPhysicalDeviceImageSlicedViewOf3DFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT, size<VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MUTABLE_DESCRIPTOR_TYPE_FEATURES_EXT, size<VkPhysicalDeviceMutableDescriptorTypeFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT, size<VkPhysicalDeviceDepthClipControlFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT, size<VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_RDMA_FEATURES_NV, size<VkPhysicalDeviceExternalMemoryRDMAFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT, size<VkPhysicalDeviceColorWriteEnableFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES, size<VkPhysicalDeviceSynchronization2Features>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES_EXT, size<VkPhysicalDeviceHostImageCopyFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT, size<VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LEGACY_DITHERING_FEATURES_EXT, size<VkPhysicalDeviceLegacyDitheringFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_FEATURES_EXT, size<VkPhysicalDeviceMultisampledRenderToSingleSampledFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_PROTECTED_ACCESS_FEATURES_EXT, size<VkPhysicalDevicePipelineProtectedAccessFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_MAINTENANCE_1_FEATURES_KHR, size<VkPhysicalDeviceVideoMaintenance1FeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INHERITED_VIEWPORT_SCISSOR_FEATURES_NV, size<VkPhysicalDeviceInheritedViewportScissorFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_2_PLANE_444_FORMATS_FEATURES_EXT, size<VkPhysicalDeviceYcbcr2Plane444FormatsFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, size<VkPhysicalDeviceProvokingVertexFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT, size<VkPhysicalDeviceDescriptorBufferFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_FEATURES, size<VkPhysicalDeviceShaderIntegerDotProductFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR, size<VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MOTION_BLUR_FEATURES_NV, size<VkPhysicalDeviceRayTracingMotionBlurFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RGBA10X6_FORMATS_FEATURES_EXT, size<VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, size<VkPhysicalDeviceDynamicRenderingFeatures>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_VIEW_MIN_LOD_FEATURES_EXT, size<VkPhysicalDeviceImageViewMinLodFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT, size<VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINEAR_COLOR_ATTACHMENT_FEATURES_NV, size<VkPhysicalDeviceLinearColorAttachmentFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT, size<VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_SET_HOST_MAPPING_FEATURES_VALVE, size<VkPhysicalDeviceDescriptorSetHostMappingFeaturesVALVE>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NESTED_COMMAND_BUFFER_FEATURES_EXT, size<VkPhysicalDeviceNestedCommandBufferFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_MODULE_IDENTIFIER_FEATURES_EXT, size<VkPhysicalDeviceShaderModuleIdentifierFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT, size<VkPhysicalDeviceImageCompressionControlFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT, size<VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBPASS_MERGE_FEEDBACK_FEATURES_EXT, size<VkPhysicalDeviceSubpassMergeFeedbackFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_OPACITY_MICROMAP_FEATURES_EXT, size<VkPhysicalDeviceOpacityMicromapFeaturesEXT>() });
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DISPLACEMENT_MICROMAP_FEATURES_NV, size<VkPhysicalDeviceDisplacementMicromapFeaturesNV>() });
+#endif
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_PROPERTIES_FEATURES_EXT, size<VkPhysicalDevicePipelinePropertiesFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_EARLY_AND_LATE_FRAGMENT_TESTS_FEATURES_AMD, size<VkPhysicalDeviceShaderEarlyAndLateFragmentTestsFeaturesAMD>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NON_SEAMLESS_CUBE_MAP_FEATURES_EXT, size<VkPhysicalDeviceNonSeamlessCubeMapFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT, size<VkPhysicalDevicePipelineRobustnessFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_PROCESSING_FEATURES_QCOM, size<VkPhysicalDeviceImageProcessingFeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TILE_PROPERTIES_FEATURES_QCOM, size<VkPhysicalDeviceTilePropertiesFeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_AMIGO_PROFILING_FEATURES_SEC, size<VkPhysicalDeviceAmigoProfilingFeaturesSEC>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT, size<VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLAMP_ZERO_ONE_FEATURES_EXT, size<VkPhysicalDeviceDepthClampZeroOneFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ADDRESS_BINDING_REPORT_FEATURES_EXT, size<VkPhysicalDeviceAddressBindingReportFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_OPTICAL_FLOW_FEATURES_NV, size<VkPhysicalDeviceOpticalFlowFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT, size<VkPhysicalDeviceFaultFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_LIBRARY_GROUP_HANDLES_FEATURES_EXT, size<VkPhysicalDevicePipelineLibraryGroupHandlesFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CORE_BUILTINS_FEATURES_ARM, size<VkPhysicalDeviceShaderCoreBuiltinsFeaturesARM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAME_BOUNDARY_FEATURES_EXT, size<VkPhysicalDeviceFrameBoundaryFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_FEATURES_EXT, size<VkPhysicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, size<VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_BIAS_CONTROL_FEATURES_EXT, size<VkPhysicalDeviceDepthBiasControlFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_INVOCATION_REORDER_FEATURES_NV, size<VkPhysicalDeviceRayTracingInvocationReorderFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_SPARSE_ADDRESS_SPACE_FEATURES_NV, size<VkPhysicalDeviceExtendedSparseAddressSpaceFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_VIEWPORTS_FEATURES_QCOM, size<VkPhysicalDeviceMultiviewPerViewViewportsFeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_POSITION_FETCH_FEATURES_KHR, size<VkPhysicalDeviceRayTracingPositionFetchFeaturesKHR>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_RENDER_AREAS_FEATURES_QCOM, size<VkPhysicalDeviceMultiviewPerViewRenderAreasFeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT, size<VkPhysicalDeviceShaderObjectFeaturesEXT>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TILE_IMAGE_FEATURES_EXT, size<VkPhysicalDeviceShaderTileImageFeaturesEXT>() });
+#ifdef VK_USE_PLATFORM_SCREEN_QNX
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_SCREEN_BUFFER_FEATURES_QNX, size<VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX>() });
+#endif
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR, size<VkPhysicalDeviceCooperativeMatrixFeaturesKHR>() });
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ENQUEUE_FEATURES_AMDX, size<VkPhysicalDeviceShaderEnqueueFeaturesAMDX>() });
+#endif
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUBIC_CLAMP_FEATURES_QCOM, size<VkPhysicalDeviceCubicClampFeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_DEGAMMA_FEATURES_QCOM, size<VkPhysicalDeviceYcbcrDegammaFeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUBIC_WEIGHTS_FEATURES_QCOM, size<VkPhysicalDeviceCubicWeightsFeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_PROCESSING_2_FEATURES_QCOM, size<VkPhysicalDeviceImageProcessing2FeaturesQCOM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_POOL_OVERALLOCATION_FEATURES_NV, size<VkPhysicalDeviceDescriptorPoolOverallocationFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PER_STAGE_DESCRIPTOR_SET_FEATURES_NV, size<VkPhysicalDevicePerStageDescriptorSetFeaturesNV>() });
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FORMAT_RESOLVE_FEATURES_ANDROID, size<VkPhysicalDeviceExternalFormatResolveFeaturesANDROID>() });
+#endif
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUDA_KERNEL_LAUNCH_FEATURES_NV, size<VkPhysicalDeviceCudaKernelLaunchFeaturesNV>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCHEDULING_CONTROLS_FEATURES_ARM, size<VkPhysicalDeviceSchedulingControlsFeaturesARM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG, size<VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RENDER_PASS_STRIPED_FEATURES_ARM, size<VkPhysicalDeviceRenderPassStripedFeaturesARM>() });
+        this->structureSize.insert({ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR, size<VkPhysicalDeviceFeatures2KHR>() });
+
+        //Initializing the full list of available structure features
+        void* pNext = nullptr;
+        physicalDeviceDeviceGeneratedCommandsFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceDeviceGeneratedCommandsFeaturesNV;
+        physicalDeviceDeviceGeneratedCommandsComputeFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceDeviceGeneratedCommandsComputeFeaturesNV;
+        physicalDevicePrivateDataFeatures.pNext = pNext;
+        pNext = &physicalDevicePrivateDataFeatures;
+        physicalDeviceVariablePointersFeatures.pNext = pNext;
+        pNext = &physicalDeviceVariablePointersFeatures;
+        physicalDeviceMultiviewFeatures.pNext = pNext;
+        pNext = &physicalDeviceMultiviewFeatures;
+        physicalDevicePresentIdFeaturesKHR.pNext = pNext;
+        pNext = &physicalDevicePresentIdFeaturesKHR;
+        physicalDevicePresentWaitFeaturesKHR.pNext = pNext;
+        pNext = &physicalDevicePresentWaitFeaturesKHR;
+        physicalDevice16BitStorageFeatures.pNext = pNext;
+        pNext = &physicalDevice16BitStorageFeatures;
+        physicalDeviceShaderSubgroupExtendedTypesFeatures.pNext = pNext;
+        pNext = &physicalDeviceShaderSubgroupExtendedTypesFeatures;
+        physicalDeviceSamplerYcbcrConversionFeatures.pNext = pNext;
+        pNext = &physicalDeviceSamplerYcbcrConversionFeatures;
+        physicalDeviceProtectedMemoryFeatures.pNext = pNext;
+        pNext = &physicalDeviceProtectedMemoryFeatures;
+        physicalDeviceBlendOperationAdvancedFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceBlendOperationAdvancedFeaturesEXT;
+        physicalDeviceMultiDrawFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceMultiDrawFeaturesEXT;
+        physicalDeviceInlineUniformBlockFeatures.pNext = pNext;
+        pNext = &physicalDeviceInlineUniformBlockFeatures;
+        physicalDeviceMaintenance4Features.pNext = pNext;
+        pNext = &physicalDeviceMaintenance4Features;
+        physicalDeviceMaintenance5FeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceMaintenance5FeaturesKHR;
+        physicalDeviceMaintenance6FeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceMaintenance6FeaturesKHR;
+        physicalDeviceShaderDrawParametersFeatures.pNext = pNext;
+        pNext = &physicalDeviceShaderDrawParametersFeatures;
+        physicalDeviceShaderFloat16Int8Features.pNext = pNext;
+        pNext = &physicalDeviceShaderFloat16Int8Features;
+        physicalDeviceHostQueryResetFeatures.pNext = pNext;
+        pNext = &physicalDeviceHostQueryResetFeatures;
+        physicalDeviceGlobalPriorityQueryFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceGlobalPriorityQueryFeaturesKHR;
+        physicalDeviceDeviceMemoryReportFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceDeviceMemoryReportFeaturesEXT;
+        physicalDeviceDescriptorIndexingFeatures.pNext = pNext;
+        pNext = &physicalDeviceDescriptorIndexingFeatures;
+        physicalDeviceTimelineSemaphoreFeatures.pNext = pNext;
+        pNext = &physicalDeviceTimelineSemaphoreFeatures;
+        physicalDevice8BitStorageFeatures.pNext = pNext;
+        pNext = &physicalDevice8BitStorageFeatures;
+        physicalDeviceConditionalRenderingFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceConditionalRenderingFeaturesEXT;
+        physicalDeviceVulkanMemoryModelFeatures.pNext = pNext;
+        pNext = &physicalDeviceVulkanMemoryModelFeatures;
+        physicalDeviceShaderAtomicInt64Features.pNext = pNext;
+        pNext = &physicalDeviceShaderAtomicInt64Features;
+        physicalDeviceShaderAtomicFloatFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceShaderAtomicFloatFeaturesEXT;
+        physicalDeviceShaderAtomicFloat2FeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceShaderAtomicFloat2FeaturesEXT;
+        physicalDeviceVertexAttributeDivisorFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceVertexAttributeDivisorFeaturesKHR;
+        physicalDeviceASTCDecodeFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceASTCDecodeFeaturesEXT;
+        physicalDeviceTransformFeedbackFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceTransformFeedbackFeaturesEXT;
+        physicalDeviceRepresentativeFragmentTestFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceRepresentativeFragmentTestFeaturesNV;
+        physicalDeviceExclusiveScissorFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceExclusiveScissorFeaturesNV;
+        physicalDeviceCornerSampledImageFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceCornerSampledImageFeaturesNV;
+        physicalDeviceComputeShaderDerivativesFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceComputeShaderDerivativesFeaturesNV;
+        physicalDeviceShaderImageFootprintFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceShaderImageFootprintFeaturesNV;
+        physicalDeviceDedicatedAllocationImageAliasingFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceDedicatedAllocationImageAliasingFeaturesNV;
+        physicalDeviceCopyMemoryIndirectFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceCopyMemoryIndirectFeaturesNV;
+        physicalDeviceMemoryDecompressionFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceMemoryDecompressionFeaturesNV;
+        physicalDeviceShadingRateImageFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceShadingRateImageFeaturesNV;
+        physicalDeviceInvocationMaskFeaturesHUAWEI.pNext = pNext;
+        pNext = &physicalDeviceInvocationMaskFeaturesHUAWEI;
+        physicalDeviceMeshShaderFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceMeshShaderFeaturesNV;
+        physicalDeviceMeshShaderFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceMeshShaderFeaturesEXT;
+        physicalDeviceAccelerationStructureFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceAccelerationStructureFeaturesKHR;
+        physicalDeviceRayTracingPipelineFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceRayTracingPipelineFeaturesKHR;
+        physicalDeviceRayQueryFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceRayQueryFeaturesKHR;
+        physicalDeviceRayTracingMaintenance1FeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceRayTracingMaintenance1FeaturesKHR;
+        physicalDeviceFragmentDensityMapFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceFragmentDensityMapFeaturesEXT;
+        physicalDeviceFragmentDensityMap2FeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceFragmentDensityMap2FeaturesEXT;
+        physicalDeviceFragmentDensityMapOffsetFeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceFragmentDensityMapOffsetFeaturesQCOM;
+        physicalDeviceScalarBlockLayoutFeatures.pNext = pNext;
+        pNext = &physicalDeviceScalarBlockLayoutFeatures;
+        physicalDeviceUniformBufferStandardLayoutFeatures.pNext = pNext;
+        pNext = &physicalDeviceUniformBufferStandardLayoutFeatures;
+        physicalDeviceDepthClipEnableFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceDepthClipEnableFeaturesEXT;
+        physicalDeviceMemoryPriorityFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceMemoryPriorityFeaturesEXT;
+        physicalDevicePageableDeviceLocalMemoryFeaturesEXT.pNext = pNext;
+        pNext = &physicalDevicePageableDeviceLocalMemoryFeaturesEXT;
+        physicalDeviceBufferDeviceAddressFeatures.pNext = pNext;
+        pNext = &physicalDeviceBufferDeviceAddressFeatures;
+        physicalDeviceBufferDeviceAddressFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceBufferDeviceAddressFeaturesEXT;
+        physicalDeviceImagelessFramebufferFeatures.pNext = pNext;
+        pNext = &physicalDeviceImagelessFramebufferFeatures;
+        physicalDeviceTextureCompressionASTCHDRFeatures.pNext = pNext;
+        pNext = &physicalDeviceTextureCompressionASTCHDRFeatures;
+        physicalDeviceCooperativeMatrixFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceCooperativeMatrixFeaturesNV;
+        physicalDeviceYcbcrImageArraysFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceYcbcrImageArraysFeaturesEXT;
+        physicalDevicePresentBarrierFeaturesNV.pNext = pNext;
+        pNext = &physicalDevicePresentBarrierFeaturesNV;
+        physicalDevicePerformanceQueryFeaturesKHR.pNext = pNext;
+        pNext = &physicalDevicePerformanceQueryFeaturesKHR;
+        physicalDeviceCoverageReductionModeFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceCoverageReductionModeFeaturesNV;
+        physicalDeviceShaderIntegerFunctions2FeaturesINTEL.pNext = pNext;
+        pNext = &physicalDeviceShaderIntegerFunctions2FeaturesINTEL;
+        physicalDeviceShaderClockFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceShaderClockFeaturesKHR;
+        physicalDeviceIndexTypeUint8FeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceIndexTypeUint8FeaturesEXT;
+        physicalDeviceShaderSMBuiltinsFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceShaderSMBuiltinsFeaturesNV;
+        physicalDeviceFragmentShaderInterlockFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceFragmentShaderInterlockFeaturesEXT;
+        physicalDeviceSeparateDepthStencilLayoutsFeatures.pNext = pNext;
+        pNext = &physicalDeviceSeparateDepthStencilLayoutsFeatures;
+        physicalDevicePrimitiveTopologyListRestartFeaturesEXT.pNext = pNext;
+        pNext = &physicalDevicePrimitiveTopologyListRestartFeaturesEXT;
+        physicalDevicePipelineExecutablePropertiesFeaturesKHR.pNext = pNext;
+        pNext = &physicalDevicePipelineExecutablePropertiesFeaturesKHR;
+        physicalDeviceShaderDemoteToHelperInvocationFeatures.pNext = pNext;
+        pNext = &physicalDeviceShaderDemoteToHelperInvocationFeatures;
+        physicalDeviceTexelBufferAlignmentFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceTexelBufferAlignmentFeaturesEXT;
+        physicalDeviceSubgroupSizeControlFeatures.pNext = pNext;
+        pNext = &physicalDeviceSubgroupSizeControlFeatures;
+        physicalDeviceLineRasterizationFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceLineRasterizationFeaturesEXT;
+        physicalDevicePipelineCreationCacheControlFeatures.pNext = pNext;
+        pNext = &physicalDevicePipelineCreationCacheControlFeatures;
+        physicalDeviceVulkan11Features.pNext = pNext;
+        pNext = &physicalDeviceVulkan11Features;
+        physicalDeviceVulkan12Features.pNext = pNext;
+        pNext = &physicalDeviceVulkan12Features;
+        physicalDeviceVulkan13Features.pNext = pNext;
+        pNext = &physicalDeviceVulkan13Features;
+        physicalDeviceCoherentMemoryFeaturesAMD.pNext = pNext;
+        pNext = &physicalDeviceCoherentMemoryFeaturesAMD;
+        physicalDeviceCustomBorderColorFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceCustomBorderColorFeaturesEXT;
+        physicalDeviceBorderColorSwizzleFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceBorderColorSwizzleFeaturesEXT;
+        physicalDeviceExtendedDynamicStateFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceExtendedDynamicStateFeaturesEXT;
+        physicalDeviceExtendedDynamicState2FeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceExtendedDynamicState2FeaturesEXT;
+        physicalDeviceExtendedDynamicState3FeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceExtendedDynamicState3FeaturesEXT;
+        physicalDeviceDiagnosticsConfigFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceDiagnosticsConfigFeaturesNV;
+        physicalDeviceZeroInitializeWorkgroupMemoryFeatures.pNext = pNext;
+        pNext = &physicalDeviceZeroInitializeWorkgroupMemoryFeatures;
+        physicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR;
+        physicalDeviceRobustness2FeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceRobustness2FeaturesEXT;
+        physicalDeviceImageRobustnessFeatures.pNext = pNext;
+        pNext = &physicalDeviceImageRobustnessFeatures;
+        physicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR;
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+        physicalDevicePortabilitySubsetFeaturesKHR.pNext = pNext;
+        pNext = &physicalDevicePortabilitySubsetFeaturesKHR;
+#endif
+        physicalDevice4444FormatsFeaturesEXT.pNext = pNext;
+        pNext = &physicalDevice4444FormatsFeaturesEXT;
+        physicalDeviceSubpassShadingFeaturesHUAWEI.pNext = pNext;
+        pNext = &physicalDeviceSubpassShadingFeaturesHUAWEI;
+        physicalDeviceClusterCullingShaderFeaturesHUAWEI.pNext = pNext;
+        pNext = &physicalDeviceClusterCullingShaderFeaturesHUAWEI;
+        physicalDeviceShaderImageAtomicInt64FeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceShaderImageAtomicInt64FeaturesEXT;
+        physicalDeviceFragmentShadingRateFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceFragmentShadingRateFeaturesKHR;
+        physicalDeviceShaderTerminateInvocationFeatures.pNext = pNext;
+        pNext = &physicalDeviceShaderTerminateInvocationFeatures;
+        physicalDeviceFragmentShadingRateEnumsFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceFragmentShadingRateEnumsFeaturesNV;
+        physicalDeviceImage2DViewOf3DFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceImage2DViewOf3DFeaturesEXT;
+        physicalDeviceImageSlicedViewOf3DFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceImageSlicedViewOf3DFeaturesEXT;
+        physicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT;
+        physicalDeviceMutableDescriptorTypeFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceMutableDescriptorTypeFeaturesEXT;
+        physicalDeviceDepthClipControlFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceDepthClipControlFeaturesEXT;
+        physicalDeviceVertexInputDynamicStateFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceVertexInputDynamicStateFeaturesEXT;
+        physicalDeviceExternalMemoryRDMAFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceExternalMemoryRDMAFeaturesNV;
+        physicalDeviceColorWriteEnableFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceColorWriteEnableFeaturesEXT;
+        physicalDeviceSynchronization2Features.pNext = pNext;
+        pNext = &physicalDeviceSynchronization2Features;
+        physicalDeviceHostImageCopyFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceHostImageCopyFeaturesEXT;
+        physicalDevicePrimitivesGeneratedQueryFeaturesEXT.pNext = pNext;
+        pNext = &physicalDevicePrimitivesGeneratedQueryFeaturesEXT;
+        physicalDeviceLegacyDitheringFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceLegacyDitheringFeaturesEXT;
+        physicalDeviceMultisampledRenderToSingleSampledFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceMultisampledRenderToSingleSampledFeaturesEXT;
+        physicalDevicePipelineProtectedAccessFeaturesEXT.pNext = pNext;
+        pNext = &physicalDevicePipelineProtectedAccessFeaturesEXT;
+        physicalDeviceVideoMaintenance1FeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceVideoMaintenance1FeaturesKHR;
+        physicalDeviceInheritedViewportScissorFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceInheritedViewportScissorFeaturesNV;
+        physicalDeviceYcbcr2Plane444FormatsFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceYcbcr2Plane444FormatsFeaturesEXT;
+        physicalDeviceProvokingVertexFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceProvokingVertexFeaturesEXT;
+        physicalDeviceDescriptorBufferFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceDescriptorBufferFeaturesEXT;
+        physicalDeviceShaderIntegerDotProductFeatures.pNext = pNext;
+        pNext = &physicalDeviceShaderIntegerDotProductFeatures;
+        physicalDeviceFragmentShaderBarycentricFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceFragmentShaderBarycentricFeaturesKHR;
+        physicalDeviceRayTracingMotionBlurFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceRayTracingMotionBlurFeaturesNV;
+        physicalDeviceRGBA10X6FormatsFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceRGBA10X6FormatsFeaturesEXT;
+        physicalDeviceDynamicRenderingFeatures.pNext = pNext;
+        pNext = &physicalDeviceDynamicRenderingFeatures;
+        physicalDeviceImageViewMinLodFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceImageViewMinLodFeaturesEXT;
+        physicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT;
+        physicalDeviceLinearColorAttachmentFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceLinearColorAttachmentFeaturesNV;
+        physicalDeviceGraphicsPipelineLibraryFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceGraphicsPipelineLibraryFeaturesEXT;
+        physicalDeviceDescriptorSetHostMappingFeaturesVALVE.pNext = pNext;
+        pNext = &physicalDeviceDescriptorSetHostMappingFeaturesVALVE;
+        physicalDeviceNestedCommandBufferFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceNestedCommandBufferFeaturesEXT;
+        physicalDeviceShaderModuleIdentifierFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceShaderModuleIdentifierFeaturesEXT;
+        physicalDeviceImageCompressionControlFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceImageCompressionControlFeaturesEXT;
+        physicalDeviceImageCompressionControlSwapchainFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceImageCompressionControlSwapchainFeaturesEXT;
+        physicalDeviceSubpassMergeFeedbackFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceSubpassMergeFeedbackFeaturesEXT;
+        physicalDeviceOpacityMicromapFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceOpacityMicromapFeaturesEXT;
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+        physicalDeviceDisplacementMicromapFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceDisplacementMicromapFeaturesNV;
+#endif
+        physicalDevicePipelinePropertiesFeaturesEXT.pNext = pNext;
+        pNext = &physicalDevicePipelinePropertiesFeaturesEXT;
+        physicalDeviceShaderEarlyAndLateFragmentTestsFeaturesAMD.pNext = pNext;
+        pNext = &physicalDeviceShaderEarlyAndLateFragmentTestsFeaturesAMD;
+        physicalDeviceNonSeamlessCubeMapFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceNonSeamlessCubeMapFeaturesEXT;
+        physicalDevicePipelineRobustnessFeaturesEXT.pNext = pNext;
+        pNext = &physicalDevicePipelineRobustnessFeaturesEXT;
+        physicalDeviceImageProcessingFeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceImageProcessingFeaturesQCOM;
+        physicalDeviceTilePropertiesFeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceTilePropertiesFeaturesQCOM;
+        physicalDeviceAmigoProfilingFeaturesSEC.pNext = pNext;
+        pNext = &physicalDeviceAmigoProfilingFeaturesSEC;
+        physicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT;
+        physicalDeviceDepthClampZeroOneFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceDepthClampZeroOneFeaturesEXT;
+        physicalDeviceAddressBindingReportFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceAddressBindingReportFeaturesEXT;
+        physicalDeviceOpticalFlowFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceOpticalFlowFeaturesNV;
+        physicalDeviceFaultFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceFaultFeaturesEXT;
+        physicalDevicePipelineLibraryGroupHandlesFeaturesEXT.pNext = pNext;
+        pNext = &physicalDevicePipelineLibraryGroupHandlesFeaturesEXT;
+        physicalDeviceShaderCoreBuiltinsFeaturesARM.pNext = pNext;
+        pNext = &physicalDeviceShaderCoreBuiltinsFeaturesARM;
+        physicalDeviceFrameBoundaryFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceFrameBoundaryFeaturesEXT;
+        physicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT;
+        physicalDeviceSwapchainMaintenance1FeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceSwapchainMaintenance1FeaturesEXT;
+        physicalDeviceDepthBiasControlFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceDepthBiasControlFeaturesEXT;
+        physicalDeviceRayTracingInvocationReorderFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceRayTracingInvocationReorderFeaturesNV;
+        physicalDeviceExtendedSparseAddressSpaceFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceExtendedSparseAddressSpaceFeaturesNV;
+        physicalDeviceMultiviewPerViewViewportsFeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceMultiviewPerViewViewportsFeaturesQCOM;
+        physicalDeviceRayTracingPositionFetchFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceRayTracingPositionFetchFeaturesKHR;
+        physicalDeviceMultiviewPerViewRenderAreasFeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceMultiviewPerViewRenderAreasFeaturesQCOM;
+        physicalDeviceShaderObjectFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceShaderObjectFeaturesEXT;
+        physicalDeviceShaderTileImageFeaturesEXT.pNext = pNext;
+        pNext = &physicalDeviceShaderTileImageFeaturesEXT;
+#ifdef VK_USE_PLATFORM_SCREEN_QNX
+        physicalDeviceExternalMemoryScreenBufferFeaturesQNX.pNext = pNext;
+        pNext = &physicalDeviceExternalMemoryScreenBufferFeaturesQNX;
+#endif
+        physicalDeviceCooperativeMatrixFeaturesKHR.pNext = pNext;
+        pNext = &physicalDeviceCooperativeMatrixFeaturesKHR;
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+        physicalDeviceShaderEnqueueFeaturesAMDX.pNext = pNext;
+        pNext = &physicalDeviceShaderEnqueueFeaturesAMDX;
+#endif
+        physicalDeviceCubicClampFeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceCubicClampFeaturesQCOM;
+        physicalDeviceYcbcrDegammaFeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceYcbcrDegammaFeaturesQCOM;
+        physicalDeviceCubicWeightsFeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceCubicWeightsFeaturesQCOM;
+        physicalDeviceImageProcessing2FeaturesQCOM.pNext = pNext;
+        pNext = &physicalDeviceImageProcessing2FeaturesQCOM;
+        physicalDeviceDescriptorPoolOverallocationFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceDescriptorPoolOverallocationFeaturesNV;
+        physicalDevicePerStageDescriptorSetFeaturesNV.pNext = pNext;
+        pNext = &physicalDevicePerStageDescriptorSetFeaturesNV;
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+        physicalDeviceExternalFormatResolveFeaturesANDROID.pNext = pNext;
+        pNext = &physicalDeviceExternalFormatResolveFeaturesANDROID;
+#endif
+        physicalDeviceCudaKernelLaunchFeaturesNV.pNext = pNext;
+        pNext = &physicalDeviceCudaKernelLaunchFeaturesNV;
+        physicalDeviceSchedulingControlsFeaturesARM.pNext = pNext;
+        pNext = &physicalDeviceSchedulingControlsFeaturesARM;
+        physicalDeviceRelaxedLineRasterizationFeaturesIMG.pNext = pNext;
+        pNext = &physicalDeviceRelaxedLineRasterizationFeaturesIMG;
+        physicalDeviceRenderPassStripedFeaturesARM.pNext = pNext;
+        pNext = &physicalDeviceRenderPassStripedFeaturesARM;
+        physicalDeviceFeatures2KHR.pNext = pNext;
+
+    }
+
+
+    VkPhysicalDeviceFeatures2KHR requiredFeaturesChain{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR, nullptr};
+    VkBaseOutStructure* current = nullptr;
+
+    void ApplyRobustness(const VpDeviceCreateInfo* pCreateInfo) {
+#ifdef VK_VERSION_1_1
+        VkPhysicalDeviceFeatures2KHR* pFeatures2 = static_cast<VkPhysicalDeviceFeatures2KHR*>(
+            vpGetStructure(&this->requiredFeaturesChain, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR));
+        if (pFeatures2 != nullptr && (pCreateInfo->flags & VP_DEVICE_CREATE_DISABLE_ROBUST_BUFFER_ACCESS_BIT)) {
+            pFeatures2->features.robustBufferAccess = VK_FALSE;
+        }
+#endif
+
+#ifdef VK_EXT_robustness2
+        VkPhysicalDeviceRobustness2FeaturesEXT* pRobustness2FeaturesEXT = static_cast<VkPhysicalDeviceRobustness2FeaturesEXT*>(
+            vpGetStructure(&this->requiredFeaturesChain, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT));
+        if (pRobustness2FeaturesEXT != nullptr) {
+            if (pCreateInfo->flags & VP_DEVICE_CREATE_DISABLE_ROBUST_BUFFER_ACCESS_BIT) {
+                pRobustness2FeaturesEXT->robustBufferAccess2 = VK_FALSE;
+            }
+            if (pCreateInfo->flags & VP_DEVICE_CREATE_DISABLE_ROBUST_IMAGE_ACCESS_BIT) {
+                pRobustness2FeaturesEXT->robustImageAccess2 = VK_FALSE;
+            }
+        }
+#endif
+#ifdef VK_EXT_image_robustness
+        VkPhysicalDeviceImageRobustnessFeaturesEXT* pImageRobustnessFeaturesEXT =
+            static_cast<VkPhysicalDeviceImageRobustnessFeaturesEXT*>(vpGetStructure(
+                &this->requiredFeaturesChain, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES_EXT));
+        if (pImageRobustnessFeaturesEXT != nullptr && (pCreateInfo->flags & VP_DEVICE_CREATE_DISABLE_ROBUST_IMAGE_ACCESS_BIT)) {
+            pImageRobustnessFeaturesEXT->robustImageAccess = VK_FALSE;
+        }
+#endif
+#ifdef VK_VERSION_1_3
+        VkPhysicalDeviceVulkan13Features* pVulkan13Features = static_cast<VkPhysicalDeviceVulkan13Features*>(
+            vpGetStructure(&this->requiredFeaturesChain, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES));
+        if (pVulkan13Features != nullptr && (pCreateInfo->flags & VP_DEVICE_CREATE_DISABLE_ROBUST_IMAGE_ACCESS_BIT)) {
+            pVulkan13Features->robustImageAccess = VK_FALSE;
+        }
+#endif
+    }
+
+    void ApplyFeatures(const VpDeviceCreateInfo* pCreateInfo) {
+        const std::size_t offset = sizeof(VkBaseOutStructure);
+        const VkBaseOutStructure* q = reinterpret_cast<const VkBaseOutStructure*>(pCreateInfo->pCreateInfo->pNext);
+        while (q) {
+            std::size_t count = this->structureSize[q->sType];
+            for (std::size_t i = 0, n = count; i < n; ++i) {
+                const VkBaseOutStructure* pInputStruct = reinterpret_cast<const VkBaseOutStructure*>(q);
+                VkBaseOutStructure* pOutputStruct = reinterpret_cast<VkBaseOutStructure*>(detail::vpGetStructure(&this->requiredFeaturesChain, q->sType));
+                const uint8_t* pInputData = reinterpret_cast<const uint8_t*>(pInputStruct) + offset;
+                uint8_t* pOutputData = reinterpret_cast<uint8_t*>(pOutputStruct) + offset;
+                const VkBool32* input = reinterpret_cast<const VkBool32*>(pInputData);
+                VkBool32* output = reinterpret_cast<VkBool32*>(pOutputData);
+
+                output[i] = (output[i] == VK_TRUE || input[i] == VK_TRUE) ? VK_TRUE : VK_FALSE;
+            }
+            q = q->pNext;
+        }
+
+        this->ApplyRobustness(pCreateInfo);
+    }
+
+    void PushBack(VkBaseOutStructure* found) { 
+        VkBaseOutStructure* last = reinterpret_cast<VkBaseOutStructure*>(&requiredFeaturesChain);
+        while (last->pNext != nullptr) {
+            last = last->pNext;
+        }
+        last->pNext = found;
+    }
+
+    void Build(const std::vector<VkStructureType>& requiredList) {
+        for (std::size_t i = 0, n = requiredList.size(); i < n; ++i) {
+            const VkStructureType sType = requiredList[i];
+            if (sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR) {
+                continue;
+            }
+
+            VkBaseOutStructure* found = vpExtractStructure(&physicalDeviceFeatures2KHR, sType);
+            if (found == nullptr) {
+                continue;
+            }
+
+            PushBack(found);
+        }
+    }
+}; // struct FeaturesChain
+
+VPAPI_ATTR const VpProfileDesc* vpGetProfileDesc(const char profileName[VP_MAX_PROFILE_NAME_SIZE]) {
+    for (uint32_t i = 0; i < profileCount; ++i) {
+        if (strncmp(profiles[i].props.profileName, profileName, VP_MAX_PROFILE_NAME_SIZE) == 0) return &profiles[i];
+    }
+    return nullptr;
+}
+
+VPAPI_ATTR std::vector<VpProfileProperties> GatherProfiles(const VpProfileProperties& profile, const char* pBlockName = nullptr) {
+    std::vector<VpProfileProperties> profiles;
+
+    if (pBlockName == nullptr) {
+        const detail::VpProfileDesc* profile_desc = detail::vpGetProfileDesc(profile.profileName);
+        if (profile_desc != nullptr) {
+            for (uint32_t profile_index = 0; profile_index < profile_desc->requiredProfileCount; ++profile_index) {
+                profiles.push_back(profile_desc->pRequiredProfiles[profile_index]);
+            }
+        }
+    }
+
+    profiles.push_back(profile);
+
+    return profiles;
+}
+
+VPAPI_ATTR bool vpCheckVersion(uint32_t actual, uint32_t expected) {
+    uint32_t actualMajor = VK_API_VERSION_MAJOR(actual);
+    uint32_t actualMinor = VK_API_VERSION_MINOR(actual);
+    uint32_t expectedMajor = VK_API_VERSION_MAJOR(expected);
+    uint32_t expectedMinor = VK_API_VERSION_MINOR(expected);
+    return actualMajor > expectedMajor || (actualMajor == expectedMajor && actualMinor >= expectedMinor);
+}
+
+VPAPI_ATTR bool HasExtension(const std::vector<VkExtensionProperties>& list, const VkExtensionProperties& element) {
+    for (std::size_t i = 0, n = list.size(); i < n; ++i) {
+        if (strcmp(list[i].extensionName, element.extensionName) == 0) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+VPAPI_ATTR bool CheckExtension(const VkExtensionProperties* supportedProperties, size_t supportedSize, const char *requestedExtension) {
+    bool found = false;
+    for (size_t i = 0, n = supportedSize; i < n; ++i) {
+        if (strcmp(supportedProperties[i].extensionName, requestedExtension) == 0) {
+            found = true;
+            break;
+            // Drivers don't actually update their spec version, so we cannot rely on this
+            // if (supportedProperties[i].specVersion >= expectedVersion) found = true;
+        }
+    }
+    VP_DEBUG_COND_MSGF(!found, "Unsupported extension: %s", requestedExtension);
+    return found;
+}
+
+VPAPI_ATTR bool CheckExtension(const std::vector<const char*>& extensions, const char* extension) {
+    for (const char* c : extensions) {
+        if (strcmp(c, extension) == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+VPAPI_ATTR void GetExtensions(uint32_t extensionCount, const VkExtensionProperties *pExtensions, std::vector<const char *> &extensions) {
+    for (uint32_t i = 0; i < extensionCount; ++i) {
+        if (CheckExtension(extensions, pExtensions[i].extensionName)) {
+            continue;
+        }
+        extensions.push_back(pExtensions[i].extensionName);
+    }
+}
+
+VPAPI_ATTR std::vector<VpBlockProperties> GatherBlocks(
+    uint32_t enabledFullProfileCount, const VpProfileProperties* pEnabledFullProfiles,
+    uint32_t enabledProfileBlockCount, const VpBlockProperties* pEnabledProfileBlocks) {
+    std::vector<VpBlockProperties> results;
+
+    for (std::size_t i = 0; i < enabledFullProfileCount; ++i) {
+        const std::vector<VpProfileProperties>& profiles = GatherProfiles(pEnabledFullProfiles[i]);
+
+        for (std::size_t j = 0; j < profiles.size(); ++j) {
+            VpBlockProperties block{profiles[j], 0, ""};
+            results.push_back(block);
+        }
+    }
+
+    for (std::size_t i = 0; i < enabledProfileBlockCount; ++i) {
+        results.push_back(pEnabledProfileBlocks[i]);
+    }
+
+    return results;
+}
+
+VPAPI_ATTR VkResult vpGetInstanceProfileSupportSingleProfile(
+    uint32_t api_version, const std::vector<VkExtensionProperties>& supported_extensions,
+    const VpProfileProperties* pProfile, VkBool32* pSupported, std::vector<VpBlockProperties>& supportedBlocks, std::vector<VpBlockProperties>& unsupportedBlocks) {
+    assert(pProfile != nullptr);
+
+    const detail::VpProfileDesc* pProfileDesc = vpGetProfileDesc(pProfile->profileName);
+    if (pProfileDesc == nullptr) {
+        *pSupported = VK_FALSE;
+        return VK_ERROR_UNKNOWN;
+    }
+
+    VpBlockProperties block{*pProfile, api_version};
+
+    if (pProfileDesc->props.specVersion < pProfile->specVersion) {
+        *pSupported = VK_FALSE;
+        unsupportedBlocks.push_back(block);
+    }
+
+    // Required API version is built in root profile, not need to check dependent profile API versions
+    if (api_version != 0) {
+        if (!vpCheckVersion(api_version, pProfileDesc->minApiVersion)) {
+            const uint32_t version_min_major = VK_API_VERSION_MAJOR(pProfileDesc->minApiVersion);
+            const uint32_t version_min_minor = VK_API_VERSION_MINOR(pProfileDesc->minApiVersion);
+            const uint32_t version_min_patch = VK_API_VERSION_PATCH(pProfileDesc->minApiVersion);
+
+            const uint32_t version_major = VK_API_VERSION_MAJOR(api_version);
+            const uint32_t version_minor = VK_API_VERSION_MINOR(api_version);
+            const uint32_t version_patch = VK_API_VERSION_PATCH(api_version);
+
+            VP_DEBUG_MSGF("Unsupported Profile API version %u.%u.%u on a Vulkan system with version %u.%u.%u", version_min_major, version_min_minor, version_min_patch, version_major, version_minor, version_patch);
+            
+            *pSupported = VK_FALSE;
+            unsupportedBlocks.push_back(block);
+        }
+    }
+
+    for (uint32_t capability_index = 0; capability_index < pProfileDesc->requiredCapabilityCount; ++capability_index) {
+        const VpCapabilitiesDesc& capabilities_desc = pProfileDesc->pRequiredCapabilities[capability_index];
+
+        VkBool32 supported_capabilities = VK_FALSE;
+        for (uint32_t variant_index = 0; variant_index < capabilities_desc.variantCount; ++variant_index) {
+            const VpVariantDesc& variant_desc = capabilities_desc.pVariants[variant_index];
+
+            VkBool32 supported_variant = VK_TRUE;
+            for (uint32_t i = 0; i < variant_desc.instanceExtensionCount; ++i) {
+                if (!detail::CheckExtension(supported_extensions.data(), supported_extensions.size(),
+                                              variant_desc.pInstanceExtensions[i].extensionName)) {
+                    supported_variant = VK_FALSE;
+                    memcpy(block.blockName, variant_desc.blockName, VP_MAX_PROFILE_NAME_SIZE * sizeof(char));
+                    unsupportedBlocks.push_back(block);
+                }
+            }
+
+            if (supported_variant == VK_TRUE) {
+                supported_capabilities = VK_TRUE;
+                memcpy(block.blockName, variant_desc.blockName, VP_MAX_PROFILE_NAME_SIZE * sizeof(char));
+                supportedBlocks.push_back(block);
+            }
+        }
+
+        if (supported_capabilities == VK_FALSE) {
+            *pSupported = VK_FALSE;
+            return VK_SUCCESS;
+        }
+    }
+
+    return VK_SUCCESS;
+}
+
+enum structure_type {
+    STRUCTURE_FEATURE = 0,
+    STRUCTURE_PROPERTY,
+    STRUCTURE_FORMAT
+};
+
+VPAPI_ATTR VkResult vpGetProfileStructureTypes(const VpProfileProperties *pProfile, const char* pBlockName, structure_type type, uint32_t *pStructureTypeCount, VkStructureType *pStructureTypes) {
+    VkResult result = pBlockName == nullptr ? VK_SUCCESS : VK_INCOMPLETE;
+
+    std::vector<VkStructureType> results;
+
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile);
+
+    for (std::size_t profile_index = 0, profile_count = profiles.size(); profile_index < profile_count; ++profile_index) {
+        const detail::VpProfileDesc* profile_desc = detail::vpGetProfileDesc(profiles[profile_index].profileName);
+        if (profile_desc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (uint32_t capability_index = 0; capability_index < profile_desc->requiredCapabilityCount; ++capability_index) {
+            const detail::VpCapabilitiesDesc& capabilities = profile_desc->pRequiredCapabilities[capability_index];
+
+            for (uint32_t variant_index = 0; variant_index < capabilities.variantCount; ++variant_index) {
+                const detail::VpVariantDesc& variant = capabilities.pVariants[variant_index];
+                if (pBlockName != nullptr) {
+                    if (strcmp(variant.blockName, pBlockName) != 0) {
+                        continue;
+                    }
+                    result = VK_SUCCESS;
+                }
+
+                uint32_t count = 0;
+                const VkStructureType* data = nullptr;
+
+                switch (type) {
+                    default:
+                    case STRUCTURE_FEATURE:
+                        count = variant.featureStructTypeCount;
+                        data = variant.pFeatureStructTypes;
+                        break;
+                    case STRUCTURE_PROPERTY:
+                        count = variant.propertyStructTypeCount;
+                        data = variant.pPropertyStructTypes;
+                        break;
+                    case STRUCTURE_FORMAT:
+                        count = variant.formatStructTypeCount;
+                        data = variant.pFormatStructTypes;
+                        break;
+                }
+
+                for (uint32_t i = 0; i < count; ++i) {
+                    const VkStructureType type = data[i];
+                    if (std::find(results.begin(), results.end(), type) == std::end(results)) {
+                        results.push_back(type);
+                    }
+                }
+            }
+        }
+    }
+
+    const uint32_t count = static_cast<uint32_t>(results.size());
+    std::sort(results.begin(), results.end());
+
+    if (pStructureTypes == nullptr) {
+        *pStructureTypeCount = count;
+    } else {
+        if (*pStructureTypeCount < count) {
+            result = VK_INCOMPLETE;
+        } else {
+            *pStructureTypeCount = count;
+        }
+
+        if (*pStructureTypeCount > 0) {
+            memcpy(pStructureTypes, &results[0], *pStructureTypeCount * sizeof(VkStructureType));
+        }
+    }
+
+    return result;
+}
+
+enum ExtensionType {
+    EXTENSION_INSTANCE,
+    EXTENSION_DEVICE,
+};
+
+VPAPI_ATTR VkResult vpGetProfileExtensionProperties(const VpProfileProperties *pProfile, const char* pBlockName, ExtensionType type, uint32_t *pPropertyCount, VkExtensionProperties *pProperties) {
+    VkResult result = pBlockName == nullptr ? VK_SUCCESS : VK_INCOMPLETE;
+
+    std::vector<VkExtensionProperties> results;
+
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile, pBlockName);
+
+    for (std::size_t profile_index = 0, profile_count = profiles.size(); profile_index < profile_count; ++profile_index) {
+        const detail::VpProfileDesc* profile_desc = detail::vpGetProfileDesc(profiles[profile_index].profileName);
+        if (profile_desc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (uint32_t capability_index = 0; capability_index < profile_desc->requiredCapabilityCount; ++capability_index) {
+            const detail::VpCapabilitiesDesc& capabilities = profile_desc->pRequiredCapabilities[capability_index];
+
+            for (uint32_t variant_index = 0; variant_index < capabilities.variantCount; ++variant_index) {
+                const detail::VpVariantDesc& variant = capabilities.pVariants[variant_index];
+                if (pBlockName != nullptr) {
+                    if (strcmp(variant.blockName, pBlockName) != 0) {
+                        continue;
+                    }
+                    result = VK_SUCCESS;
+                }
+
+                switch (type) {
+                    default:
+                    case EXTENSION_INSTANCE:
+                        for (uint32_t i = 0; i < variant.instanceExtensionCount; ++i) {
+                            if (detail::HasExtension(results, variant.pInstanceExtensions[i])) {
+                                continue;
+                            }
+                            results.push_back(variant.pInstanceExtensions[i]);
+                        }
+                        break;
+                    case EXTENSION_DEVICE:
+                        for (uint32_t i = 0; i < variant.deviceExtensionCount; ++i) {
+                            if (detail::HasExtension(results, variant.pDeviceExtensions[i])) {
+                                continue;
+                            }
+                            results.push_back(variant.pDeviceExtensions[i]);
+                        }
+                        break;
+                }
+            }
+        }
+    }
+
+    const uint32_t count = static_cast<uint32_t>(results.size());
+
+    if (pProperties == nullptr) {
+        *pPropertyCount = count;
+    } else {
+        if (*pPropertyCount < count) {
+            result = VK_INCOMPLETE;
+        } else {
+            *pPropertyCount = count;
+        }
+        if (*pPropertyCount > 0) {
+            memcpy(pProperties, &results[0], *pPropertyCount * sizeof(VkExtensionProperties));
+        }
+    }
+
+    return result;
+}
+
+} // namespace detail
+
+VPAPI_ATTR VkResult vpGetProfiles(uint32_t *pPropertyCount, VpProfileProperties *pProperties) {
+    VkResult result = VK_SUCCESS;
+
+    if (pProperties == nullptr) {
+        *pPropertyCount = detail::profileCount;
+    } else {
+        if (*pPropertyCount < detail::profileCount) {
+            result = VK_INCOMPLETE;
+        } else {
+            *pPropertyCount = detail::profileCount;
+        }
+        for (uint32_t i = 0; i < *pPropertyCount; ++i) {
+            pProperties[i] = detail::profiles[i].props;
+        }
+    }
+    return result;
+}
+
+VPAPI_ATTR VkResult vpGetProfileRequiredProfiles(const VpProfileProperties *pProfile, uint32_t *pPropertyCount, VpProfileProperties *pProperties) {
+    VkResult result = VK_SUCCESS;
+
+    const detail::VpProfileDesc* pDesc = detail::vpGetProfileDesc(pProfile->profileName);
+    if (pDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+    if (pProperties == nullptr) {
+        *pPropertyCount = pDesc->requiredProfileCount;
+    } else {
+        if (*pPropertyCount < pDesc->requiredProfileCount) {
+            result = VK_INCOMPLETE;
+        } else {
+            *pPropertyCount = pDesc->requiredProfileCount;
+        }
+        for (uint32_t i = 0; i < *pPropertyCount; ++i) {
+            pProperties[i] = pDesc->pRequiredProfiles[i];
+        }
+    }
+    return result;
+}
+
+VPAPI_ATTR uint32_t vpGetProfileAPIVersion(const VpProfileProperties* pProfile) {
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile, nullptr);
+
+    uint32_t major = 0;
+    uint32_t minor = 0;
+    uint32_t patch = 0;
+
+    for (std::size_t i = 0, n = profiles.size(); i < n; ++i) {
+        const detail::VpProfileDesc* pDesc = detail::vpGetProfileDesc(profiles[i].profileName);
+        if (pDesc == nullptr) return 0;
+
+        major = std::max<uint32_t>(major, VK_API_VERSION_MAJOR(pDesc->minApiVersion));
+        minor = std::max<uint32_t>(minor, VK_API_VERSION_MINOR(pDesc->minApiVersion));
+        patch = std::max<uint32_t>(patch, VK_API_VERSION_PATCH(pDesc->minApiVersion));
+    }
+
+    return VK_MAKE_API_VERSION(0, major, minor, patch);
+}
+
+VPAPI_ATTR VkResult vpGetProfileFallbacks(const VpProfileProperties *pProfile, uint32_t *pPropertyCount, VpProfileProperties *pProperties) {
+    VkResult result = VK_SUCCESS;
+
+    const detail::VpProfileDesc* pDesc = detail::vpGetProfileDesc(pProfile->profileName);
+    if (pDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+    if (pProperties == nullptr) {
+        *pPropertyCount = pDesc->fallbackCount;
+    } else {
+        if (*pPropertyCount < pDesc->fallbackCount) {
+            result = VK_INCOMPLETE;
+        } else {
+            *pPropertyCount = pDesc->fallbackCount;
+        }
+        for (uint32_t i = 0; i < *pPropertyCount; ++i) {
+            pProperties[i] = pDesc->pFallbacks[i];
+        }
+    }
+    return result;
+}
+
+VPAPI_ATTR VkResult vpHasMultipleVariantsProfile(const VpProfileProperties *pProfile, VkBool32 *pHasMultipleVariants) {
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile, nullptr);
+
+    for (std::size_t profile_index = 0, profile_count = profiles.size(); profile_index < profile_count; ++profile_index) {
+        const detail::VpProfileDesc* pDesc = detail::vpGetProfileDesc(profiles[profile_index].profileName);
+        if (pDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (uint32_t capabilities_index = 0, n = pDesc->requiredCapabilityCount; capabilities_index < n; ++capabilities_index) {
+            if (pDesc->pRequiredCapabilities[capabilities_index].variantCount > 1) {
+                *pHasMultipleVariants = VK_TRUE;
+                return VK_SUCCESS;
+            }
+        }
+    }
+
+    *pHasMultipleVariants = VK_FALSE;
+    return VK_SUCCESS;
+}
+
+VPAPI_ATTR VkResult vpGetInstanceProfileVariantsSupport(const char *pLayerName, const VpProfileProperties *pProfile, VkBool32 *pSupported, uint32_t *pPropertyCount, VpBlockProperties* pProperties) {
+    VkResult result = VK_SUCCESS;
+
+    uint32_t api_version = VK_MAKE_API_VERSION(0, 1, 0, 0);
+    static PFN_vkEnumerateInstanceVersion pfnEnumerateInstanceVersion =
+        (PFN_vkEnumerateInstanceVersion)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion");
+    if (pfnEnumerateInstanceVersion != nullptr) {
+        result = pfnEnumerateInstanceVersion(&api_version);
+        if (result != VK_SUCCESS) {
+            *pSupported = VK_FALSE;
+            return result;
+        }
+    }
+
+    uint32_t supported_instance_extension_count = 0;
+    result = vkEnumerateInstanceExtensionProperties(pLayerName, &supported_instance_extension_count, nullptr);
+    if (result != VK_SUCCESS) {
+        *pSupported = VK_FALSE;
+        return result;
+    }
+    std::vector<VkExtensionProperties> supported_instance_extensions;
+    if (supported_instance_extension_count > 0) {
+        supported_instance_extensions.resize(supported_instance_extension_count);
+    }
+    result = vkEnumerateInstanceExtensionProperties(pLayerName, &supported_instance_extension_count, supported_instance_extensions.data());
+    if (result != VK_SUCCESS) {
+        *pSupported = VK_FALSE;
+        return result;
+    }
+
+    VkBool32 supported = VK_TRUE;
+
+    // We require VK_KHR_get_physical_device_properties2 if we are on Vulkan 1.0
+    if (api_version < VK_API_VERSION_1_1) {
+        bool foundGPDP2 = false;
+        for (size_t i = 0; i < supported_instance_extensions.size(); ++i) {
+            if (strcmp(supported_instance_extensions[i].extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) {
+                foundGPDP2 = true;
+                break;
+            }
+        }
+        if (!foundGPDP2) {
+            VP_DEBUG_MSG("Unsupported mandatory extension VK_KHR_get_physical_device_properties2 on Vulkan 1.0");
+            supported = VK_FALSE;
+        }
+    }
+
+    const detail::VpProfileDesc* pProfileDesc = detail::vpGetProfileDesc(pProfile->profileName);
+    if (pProfileDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+    std::vector<VpBlockProperties> supported_blocks;
+    std::vector<VpBlockProperties> unsupported_blocks;
+
+    result = detail::vpGetInstanceProfileSupportSingleProfile(api_version, supported_instance_extensions, pProfile, &supported, supported_blocks, unsupported_blocks);
+    if (result != VK_SUCCESS) {
+        *pSupported = supported;
+        return result;
+    }
+ 
+    for (std::size_t i = 0; i < pProfileDesc->requiredProfileCount; ++i) {
+        result = detail::vpGetInstanceProfileSupportSingleProfile(0, supported_instance_extensions, &pProfileDesc->pRequiredProfiles[i], &supported, supported_blocks, unsupported_blocks);
+        if (result != VK_SUCCESS) {
+            *pSupported = supported;
+            return result;
+        }
+    }
+
+    const std::vector<VpBlockProperties>& blocks = supported ? supported_blocks : unsupported_blocks;
+
+    if (pProperties == nullptr) {
+        *pPropertyCount = static_cast<uint32_t>(blocks.size());
+    } else {
+        if (*pPropertyCount < static_cast<uint32_t>(blocks.size())) {
+            result = VK_INCOMPLETE;
+        } else {
+            *pPropertyCount = static_cast<uint32_t>(blocks.size());
+        }
+        for (uint32_t i = 0, n = static_cast<uint32_t>(blocks.size()); i < n; ++i) {
+            pProperties[i] = blocks[i];
+        }
+    }
+
+    *pSupported = supported;
+    return result;
+}
+
+VPAPI_ATTR VkResult vpGetInstanceProfileSupport(const char *pLayerName, const VpProfileProperties *pProfile, VkBool32 *pSupported) {
+    uint32_t count = 0;
+    return vpGetInstanceProfileVariantsSupport(pLayerName, pProfile, pSupported, &count, nullptr);
+}
+
+
+VPAPI_ATTR VkResult vpCreateInstance(const VpInstanceCreateInfo *pCreateInfo,
+                                     const VkAllocationCallbacks *pAllocator, VkInstance *pInstance) {
+    if (pCreateInfo == nullptr || pInstance == nullptr) {
+        return vkCreateInstance(pCreateInfo == nullptr ? nullptr : pCreateInfo->pCreateInfo, pAllocator, pInstance);
+    }
+
+    const std::vector<VpBlockProperties>& blocks = detail::GatherBlocks(
+        pCreateInfo->enabledFullProfileCount, pCreateInfo->pEnabledFullProfiles,
+        pCreateInfo->enabledProfileBlockCount, pCreateInfo->pEnabledProfileBlocks);
+
+    std::vector<const char*> extensions;
+    for (std::uint32_t i = 0, n = pCreateInfo->pCreateInfo->enabledExtensionCount; i < n; ++i) {
+        extensions.push_back(pCreateInfo->pCreateInfo->ppEnabledExtensionNames[i]);
+    }
+
+    for (std::size_t i = 0, n = blocks.size(); i < n; ++i) {
+        const detail::VpProfileDesc* pProfileDesc = detail::vpGetProfileDesc(blocks[i].profiles.profileName);
+        if (pProfileDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (std::size_t j = 0, p = pProfileDesc->requiredCapabilityCount; j < p; ++j) {
+            const detail::VpCapabilitiesDesc* pCapsDesc = &pProfileDesc->pRequiredCapabilities[j];
+
+            for (std::size_t v = 0, q = pCapsDesc->variantCount; v < q; ++v) {
+                const detail::VpVariantDesc* variant = &pCapsDesc->pVariants[v];
+
+                if (strcmp(blocks[i].blockName, "") != 0) {
+                    if (strcmp(variant->blockName, blocks[i].blockName) != 0) {
+                        continue;
+                    }
+                }
+
+                detail::GetExtensions(variant->instanceExtensionCount, variant->pInstanceExtensions, extensions);
+            }
+        }
+    }
+
+    VkApplicationInfo appInfo{VK_STRUCTURE_TYPE_APPLICATION_INFO};
+    if (pCreateInfo->pCreateInfo->pApplicationInfo != nullptr) {
+        appInfo = *pCreateInfo->pCreateInfo->pApplicationInfo;
+    } else if (!blocks.empty()) {
+        appInfo.apiVersion = vpGetProfileAPIVersion(&blocks[0].profiles);
+    }
+
+    VkInstanceCreateInfo createInfo = *pCreateInfo->pCreateInfo;
+    createInfo.pApplicationInfo = &appInfo;
+
+    // Need to include VK_KHR_get_physical_device_properties2 if we are on Vulkan 1.0
+    if (createInfo.pApplicationInfo->apiVersion < VK_API_VERSION_1_1) {
+        bool foundGPDP2 = false;
+        for (size_t i = 0; i < extensions.size(); ++i) {
+            if (strcmp(extensions[i], VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) {
+                foundGPDP2 = true;
+                break;
+            }
+        }
+        if (!foundGPDP2) {
+            extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+        }
+    }
+
+#ifdef __APPLE__
+    bool has_portability_ext = false;
+    for (std::size_t i = 0, n = extensions.size(); i < n; ++i) {
+        if (strcmp(extensions[i], VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0) {
+            has_portability_ext = true;
+            break;
+        }
+    }
+
+    if (!has_portability_ext) {
+        extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
+    }
+
+    createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+#endif
+
+    if (!extensions.empty()) {
+        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
+        createInfo.ppEnabledExtensionNames = extensions.data();
+    }
+
+    return vkCreateInstance(&createInfo, pAllocator, pInstance);
+}
+
+VPAPI_ATTR VkResult vpGetPhysicalDeviceProfileVariantsSupport(VkInstance instance, VkPhysicalDevice physicalDevice,
+                                                              const VpProfileProperties *pProfile, VkBool32 *pSupported, uint32_t *pPropertyCount, VpBlockProperties* pProperties) {
+    VkResult result = VK_SUCCESS;
+
+    uint32_t supported_device_extension_count = 0;
+    result = vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &supported_device_extension_count, nullptr);
+    if (result != VK_SUCCESS) {
+        return result;
+    }
+    std::vector<VkExtensionProperties> supported_device_extensions;
+    if (supported_device_extension_count > 0) {
+        supported_device_extensions.resize(supported_device_extension_count);
+    }
+    result = vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &supported_device_extension_count, supported_device_extensions.data());
+    if (result != VK_SUCCESS) {
+        return result;
+    }
+
+    // Workaround old loader bug where count could be smaller on the second call to vkEnumerateDeviceExtensionProperties
+    if (supported_device_extension_count > 0) {
+        supported_device_extensions.resize(supported_device_extension_count);
+    }
+
+    const detail::VpProfileDesc* pProfileDesc = detail::vpGetProfileDesc(pProfile->profileName);
+    if (pProfileDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+    struct GPDP2EntryPoints {
+        PFN_vkGetPhysicalDeviceFeatures2KHR                 pfnGetPhysicalDeviceFeatures2;
+        PFN_vkGetPhysicalDeviceProperties2KHR               pfnGetPhysicalDeviceProperties2;
+        PFN_vkGetPhysicalDeviceFormatProperties2KHR         pfnGetPhysicalDeviceFormatProperties2;
+        PFN_vkGetPhysicalDeviceQueueFamilyProperties2KHR    pfnGetPhysicalDeviceQueueFamilyProperties2;
+    };
+
+    std::vector<VpBlockProperties> supported_blocks;
+    std::vector<VpBlockProperties> unsupported_blocks;
+
+    struct UserData {
+        VkPhysicalDevice physicalDevice;
+        std::vector<VpBlockProperties>& supported_blocks;
+        std::vector<VpBlockProperties>& unsupported_blocks;
+        const detail::VpVariantDesc* variant;
+        GPDP2EntryPoints gpdp2;
+        uint32_t index;
+        uint32_t count;
+        detail::PFN_vpStructChainerCb pfnCb;
+        bool supported;
+    } userData{physicalDevice, supported_blocks, unsupported_blocks};
+
+    // Attempt to load core versions of the GPDP2 entry points
+    userData.gpdp2.pfnGetPhysicalDeviceFeatures2 =
+        (PFN_vkGetPhysicalDeviceFeatures2KHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2");
+    userData.gpdp2.pfnGetPhysicalDeviceProperties2 =
+        (PFN_vkGetPhysicalDeviceProperties2KHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2");
+    userData.gpdp2.pfnGetPhysicalDeviceFormatProperties2 =
+        (PFN_vkGetPhysicalDeviceFormatProperties2KHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties2");
+    userData.gpdp2.pfnGetPhysicalDeviceQueueFamilyProperties2 =
+        (PFN_vkGetPhysicalDeviceQueueFamilyProperties2KHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceQueueFamilyProperties2");
+
+    // If not successful, try to load KHR variant
+    if (userData.gpdp2.pfnGetPhysicalDeviceFeatures2 == nullptr) {
+        userData.gpdp2.pfnGetPhysicalDeviceFeatures2 =
+            (PFN_vkGetPhysicalDeviceFeatures2KHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2KHR");
+        userData.gpdp2.pfnGetPhysicalDeviceProperties2 =
+            (PFN_vkGetPhysicalDeviceProperties2KHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR");
+        userData.gpdp2.pfnGetPhysicalDeviceFormatProperties2 =
+            (PFN_vkGetPhysicalDeviceFormatProperties2KHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties2KHR");
+        userData.gpdp2.pfnGetPhysicalDeviceQueueFamilyProperties2 =
+            (PFN_vkGetPhysicalDeviceQueueFamilyProperties2KHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceQueueFamilyProperties2KHR");
+    }
+
+    if (userData.gpdp2.pfnGetPhysicalDeviceFeatures2 == nullptr ||
+        userData.gpdp2.pfnGetPhysicalDeviceProperties2 == nullptr ||
+        userData.gpdp2.pfnGetPhysicalDeviceFormatProperties2 == nullptr ||
+        userData.gpdp2.pfnGetPhysicalDeviceQueueFamilyProperties2 == nullptr) {
+        return VK_ERROR_EXTENSION_NOT_PRESENT;
+    }
+
+    VP_DEBUG_MSGF("Checking device support for profile %s (%s). You may find the details of the capabilities of this device on https://vulkan.gpuinfo.org/", pProfile->profileName, detail::vpGetDeviceAndDriverInfoString(physicalDevice, userData.gpdp2.pfnGetPhysicalDeviceProperties2).c_str());
+
+    bool supported = true;
+
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile);
+
+    for (std::size_t i = 0, n = profiles.size(); i < n; ++i) {
+        const char* profile_name = profiles[i].profileName;
+
+        const detail::VpProfileDesc* pProfileDesc = detail::vpGetProfileDesc(profile_name);
+        if (pProfileDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+        bool supported_profile = true;
+
+
+        if (pProfileDesc->props.specVersion < pProfile->specVersion) {
+            supported_profile = false;
+        }
+
+        VpBlockProperties block{profiles[i], pProfileDesc->minApiVersion};
+
+        VkPhysicalDeviceProperties props{};
+        vkGetPhysicalDeviceProperties(physicalDevice, &props);
+        if (!detail::vpCheckVersion(props.apiVersion, pProfileDesc->minApiVersion)) {
+            VP_DEBUG_MSGF("Unsupported API version: %u.%u.%u", VK_API_VERSION_MAJOR(pProfileDesc->minApiVersion), VK_API_VERSION_MINOR(pProfileDesc->minApiVersion), VK_API_VERSION_PATCH(pProfileDesc->minApiVersion));
+            supported_profile = false;
+        }
+
+        for (uint32_t required_capability_index = 0; required_capability_index < pProfileDesc->requiredCapabilityCount; ++required_capability_index) {
+            const detail::VpCapabilitiesDesc* required_capabilities = &pProfileDesc->pRequiredCapabilities[required_capability_index];
+
+            bool supported_block = false;
+
+            for (uint32_t variant_index = 0; variant_index < required_capabilities->variantCount; ++variant_index) {
+                const detail::VpVariantDesc& variant_desc = required_capabilities->pVariants[variant_index];
+
+                bool supported_variant = true;
+
+                for (uint32_t i = 0; i < variant_desc.deviceExtensionCount; ++i) {
+                    const char *requested_extension = variant_desc.pDeviceExtensions[i].extensionName;
+                    if (!detail::CheckExtension(supported_device_extensions.data(), supported_device_extensions.size(), requested_extension)) {
+                        supported_variant = false;
+                    }
+                }
+
+                userData.variant = &variant_desc;
+
+                VkPhysicalDeviceFeatures2KHR features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR };
+                userData.variant->chainers.pfnFeature(
+                    static_cast<VkBaseOutStructure*>(static_cast<void*>(&features)), &userData,
+                    [](VkBaseOutStructure* p, void* pUser) {
+                        UserData* pUserData = static_cast<UserData*>(pUser);
+                        pUserData->gpdp2.pfnGetPhysicalDeviceFeatures2(pUserData->physicalDevice,
+                                                                        static_cast<VkPhysicalDeviceFeatures2KHR*>(static_cast<void*>(p)));
+                        pUserData->supported = true;
+                        while (p != nullptr) {
+                            if (!pUserData->variant->feature.pfnComparator(p)) {
+                                pUserData->supported = false;
+                            }
+                            p = p->pNext;
+                        }
+                    }
+                );
+                if (!userData.supported) {
+                    supported_variant = false;
+                }
+
+                VkPhysicalDeviceProperties2KHR props{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR };
+                userData.variant->chainers.pfnProperty(
+                    static_cast<VkBaseOutStructure*>(static_cast<void*>(&props)), &userData,
+                    [](VkBaseOutStructure* p, void* pUser) {
+                        UserData* pUserData = static_cast<UserData*>(pUser);
+                        pUserData->gpdp2.pfnGetPhysicalDeviceProperties2(pUserData->physicalDevice,
+                                                                         static_cast<VkPhysicalDeviceProperties2KHR*>(static_cast<void*>(p)));
+                        pUserData->supported = true;
+                        while (p != nullptr) {
+                            if (!pUserData->variant->property.pfnComparator(p)) {
+                                pUserData->supported = false;
+                            }
+                            p = p->pNext;
+                        }
+                    }
+                );
+                if (!userData.supported) {
+                    supported_variant = false;
+                }
+
+                for (uint32_t i = 0; i < userData.variant->formatCount && supported_variant; ++i) {
+                    userData.index = i;
+                    VkFormatProperties2KHR props{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR };
+                    userData.variant->chainers.pfnFormat(
+                        static_cast<VkBaseOutStructure*>(static_cast<void*>(&props)), &userData,
+                        [](VkBaseOutStructure* p, void* pUser) {
+                            UserData* pUserData = static_cast<UserData*>(pUser);
+                            pUserData->gpdp2.pfnGetPhysicalDeviceFormatProperties2(pUserData->physicalDevice, pUserData->variant->pFormats[pUserData->index].format,
+                                                                                   static_cast<VkFormatProperties2KHR*>(static_cast<void*>(p)));
+                            pUserData->supported = true;
+                            while (p != nullptr) {
+                                if (!pUserData->variant->pFormats[pUserData->index].pfnComparator(p)) {
+                                    pUserData->supported = false;
+                                }
+                                p = p->pNext;
+                            }
+                        }
+                    );
+                    if (!userData.supported) {
+                        supported_variant = false;
+                    }
+                }
+
+                memcpy(block.blockName, variant_desc.blockName, VP_MAX_PROFILE_NAME_SIZE * sizeof(char));
+                if (supported_variant) {
+                    supported_blocks.push_back(block);
+                    supported_block = true;
+                    break;
+                } else {
+                    unsupported_blocks.push_back(block);
+                }
+            }
+
+            if (!supported_block) {
+                supported_profile = false;
+            }
+        }
+
+        if (!supported_profile) {
+            supported = false;
+        }
+    }
+
+    const std::vector<VpBlockProperties>& blocks = supported ? supported_blocks : unsupported_blocks;
+
+    if (pProperties == nullptr) {
+        *pPropertyCount = static_cast<uint32_t>(blocks.size());
+    } else {
+        if (*pPropertyCount < static_cast<uint32_t>(blocks.size())) {
+            result = VK_INCOMPLETE;
+        } else {
+            *pPropertyCount = static_cast<uint32_t>(blocks.size());
+        }
+        for (uint32_t i = 0, n = static_cast<uint32_t>(blocks.size()); i < n; ++i) {
+            pProperties[i] = blocks[i];
+        }
+    }
+
+    *pSupported = supported ? VK_TRUE : VK_FALSE;
+    return VK_SUCCESS;
+}
+
+VPAPI_ATTR VkResult vpGetPhysicalDeviceProfileSupport(VkInstance instance, VkPhysicalDevice physicalDevice,
+                                                      const VpProfileProperties *pProfile, VkBool32 *pSupported) {
+    uint32_t count = 0;
+    return vpGetPhysicalDeviceProfileVariantsSupport(instance, physicalDevice, pProfile, pSupported, &count, nullptr);
+}
+
+VPAPI_ATTR VkResult vpCreateDevice(VkPhysicalDevice physicalDevice, const VpDeviceCreateInfo *pCreateInfo,
+                                   const VkAllocationCallbacks *pAllocator, VkDevice *pDevice) {
+    if (physicalDevice == VK_NULL_HANDLE || pCreateInfo == nullptr || pDevice == nullptr) {
+        return vkCreateDevice(physicalDevice, pCreateInfo == nullptr ? nullptr : pCreateInfo->pCreateInfo, pAllocator, pDevice);
+    }
+
+    const std::vector<VpBlockProperties>& blocks = detail::GatherBlocks(
+        pCreateInfo->enabledFullProfileCount, pCreateInfo->pEnabledFullProfiles,
+        pCreateInfo->enabledProfileBlockCount, pCreateInfo->pEnabledProfileBlocks);
+
+    std::unique_ptr<detail::FeaturesChain> chain = std::make_unique<detail::FeaturesChain>();
+    std::vector<VkStructureType> structureTypes;
+
+    std::vector<const char*> extensions;
+    for (std::uint32_t i = 0, n = pCreateInfo->pCreateInfo->enabledExtensionCount; i < n; ++i) {
+        extensions.push_back(pCreateInfo->pCreateInfo->ppEnabledExtensionNames[i]);
+    }
+
+    for (std::size_t i = 0, n = blocks.size(); i < n; ++i) {
+        const detail::VpProfileDesc* pProfileDesc = detail::vpGetProfileDesc(blocks[i].profiles.profileName);
+        if (pProfileDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (std::size_t j = 0, p = pProfileDesc->requiredCapabilityCount; j < p; ++j) {
+            const detail::VpCapabilitiesDesc* pCapsDesc = &pProfileDesc->pRequiredCapabilities[j];
+
+            for (std::size_t v = 0, q = pCapsDesc->variantCount; v < q; ++v) {
+                const detail::VpVariantDesc* variant = &pCapsDesc->pVariants[v];
+
+                if (strcmp(blocks[i].blockName, "") != 0) {
+                    if (strcmp(variant->blockName, blocks[i].blockName) != 0) {
+                        continue;
+                    }
+                }
+
+                for (uint32_t t = 0; t < variant->featureStructTypeCount; ++t) {
+                    const VkStructureType type = variant->pFeatureStructTypes[t];
+                    if (std::find(structureTypes.begin(), structureTypes.end(), type) == std::end(structureTypes)) {
+                        structureTypes.push_back(type);
+                    }
+                }
+
+                detail::GetExtensions(variant->deviceExtensionCount, variant->pDeviceExtensions, extensions);
+            }
+        }
+    }
+
+    VkBaseOutStructure* pNext = static_cast<VkBaseOutStructure*>(const_cast<void*>(pCreateInfo->pCreateInfo->pNext));
+    detail::GatherStructureTypes(structureTypes, pNext);
+
+    chain->Build(structureTypes);
+
+    VkPhysicalDeviceFeatures2KHR* pFeatures = &chain->requiredFeaturesChain;
+    if (pCreateInfo->pCreateInfo->pEnabledFeatures) {
+        pFeatures->features = *pCreateInfo->pCreateInfo->pEnabledFeatures;
+    }
+
+    for (std::size_t i = 0, n = blocks.size(); i < n; ++i) {
+        const detail::VpProfileDesc* pProfileDesc = detail::vpGetProfileDesc(blocks[i].profiles.profileName);
+        if (pProfileDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (std::size_t j = 0, p = pProfileDesc->requiredCapabilityCount; j < p; ++j) {
+            const detail::VpCapabilitiesDesc* pCapsDesc = &pProfileDesc->pRequiredCapabilities[j];
+
+            for (std::size_t v = 0, q = pCapsDesc->variantCount; v < q; ++v) {
+                const detail::VpVariantDesc* variant = &pCapsDesc->pVariants[v];
+
+                VkBaseOutStructure* p = reinterpret_cast<VkBaseOutStructure*>(pFeatures);
+                if (variant->feature.pfnFiller != nullptr) {
+                    while (p != nullptr) {
+                        variant->feature.pfnFiller(p);
+                        p = p->pNext;
+                    }
+                }
+            }
+        }
+    }
+
+    chain->ApplyFeatures(pCreateInfo);
+
+    if (pCreateInfo->flags & VP_DEVICE_CREATE_DISABLE_ROBUST_BUFFER_ACCESS_BIT) {
+        pFeatures->features.robustBufferAccess = VK_FALSE;
+    }
+
+    VkDeviceCreateInfo createInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
+    createInfo.pNext = &chain->requiredFeaturesChain;
+    createInfo.queueCreateInfoCount = pCreateInfo->pCreateInfo->queueCreateInfoCount;
+    createInfo.pQueueCreateInfos = pCreateInfo->pCreateInfo->pQueueCreateInfos;
+    createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
+    createInfo.ppEnabledExtensionNames = extensions.data();
+
+    return vkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice);
+}
+
+VPAPI_ATTR VkResult vpGetProfileInstanceExtensionProperties(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties) {
+    return detail::vpGetProfileExtensionProperties(pProfile, pBlockName, detail::EXTENSION_INSTANCE, pPropertyCount, pProperties);
+}
+
+VPAPI_ATTR VkResult vpGetProfileDeviceExtensionProperties(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties) {
+    return detail::vpGetProfileExtensionProperties(pProfile, pBlockName, detail::EXTENSION_DEVICE, pPropertyCount, pProperties);
+}
+
+VPAPI_ATTR VkResult vpGetProfileFeatures(const VpProfileProperties *pProfile, const char* pBlockName, void *pNext) {
+    VkResult result = pBlockName == nullptr ? VK_SUCCESS : VK_INCOMPLETE;
+
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile);
+
+    for (std::size_t profile_index = 0, profile_count = profiles.size(); profile_index < profile_count; ++profile_index) {
+        const detail::VpProfileDesc* profile_desc = detail::vpGetProfileDesc(profiles[profile_index].profileName);
+        if (profile_desc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (uint32_t capability_index = 0; capability_index < profile_desc->requiredCapabilityCount; ++capability_index) {
+            const detail::VpCapabilitiesDesc& capabilities = profile_desc->pRequiredCapabilities[capability_index];
+
+            for (uint32_t variant_index = 0; variant_index < capabilities.variantCount; ++variant_index) {
+                const detail::VpVariantDesc& variant = capabilities.pVariants[variant_index];
+                if (pBlockName != nullptr) {
+                    if (strcmp(variant.blockName, pBlockName) != 0) {
+                        continue;
+                    }
+                    result = VK_SUCCESS;
+                }
+
+                if (variant.feature.pfnFiller == nullptr) continue;
+
+                VkBaseOutStructure* p = static_cast<VkBaseOutStructure*>(pNext);
+                while (p != nullptr) {
+                    variant.feature.pfnFiller(p);
+                    p = p->pNext;
+                }
+            }
+        }
+    }
+
+    return result;
+}
+
+VPAPI_ATTR VkResult vpGetProfileProperties(const VpProfileProperties *pProfile, const char* pBlockName, void *pNext) {
+    VkResult result = pBlockName == nullptr ? VK_SUCCESS : VK_INCOMPLETE;
+
+    VkBool32 multiple_variants = VK_FALSE;
+    if (vpHasMultipleVariantsProfile(pProfile, &multiple_variants) == VK_ERROR_UNKNOWN) {
+        return VK_ERROR_UNKNOWN;
+    }
+    if (multiple_variants == VK_TRUE && pBlockName == nullptr) {
+        return VK_ERROR_UNKNOWN;
+    }
+
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile);
+
+    for (std::size_t profile_index = 0, profile_count = profiles.size(); profile_index < profile_count; ++profile_index) {
+        const detail::VpProfileDesc* profile_desc = detail::vpGetProfileDesc(profiles[profile_index].profileName);
+        if (profile_desc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (uint32_t capability_index = 0; capability_index < profile_desc->requiredCapabilityCount; ++capability_index) {
+            const detail::VpCapabilitiesDesc& capabilities = profile_desc->pRequiredCapabilities[capability_index];
+
+            for (uint32_t variant_index = 0; variant_index < capabilities.variantCount; ++variant_index) {
+                const detail::VpVariantDesc& variant = capabilities.pVariants[variant_index];
+                if (pBlockName != nullptr) {
+                    if (strcmp(variant.blockName, pBlockName) != 0) {
+                        continue;
+                    }
+                    result = VK_SUCCESS;
+                }
+
+                if (variant.property.pfnFiller == nullptr) continue;
+                
+                VkBaseOutStructure* p = static_cast<VkBaseOutStructure*>(pNext);
+                while (p != nullptr) {
+                    variant.property.pfnFiller(p);
+                    p = p->pNext;
+                }
+            }
+        }
+    }
+
+    return result;
+}
+
+VPAPI_ATTR VkResult vpGetProfileFormats(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pFormatCount, VkFormat *pFormats) {
+    VkResult result = pBlockName == nullptr ? VK_SUCCESS : VK_INCOMPLETE;
+
+    std::vector<VkFormat> results;
+
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile);
+
+    for (std::size_t profile_index = 0, profile_count = profiles.size(); profile_index < profile_count; ++profile_index) {
+        const detail::VpProfileDesc* profile_desc = detail::vpGetProfileDesc(profiles[profile_index].profileName);
+        if (profile_desc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (uint32_t capability_index = 0; capability_index < profile_desc->requiredCapabilityCount; ++capability_index) {
+            const detail::VpCapabilitiesDesc& capabilities = profile_desc->pRequiredCapabilities[capability_index];
+
+            for (uint32_t variant_index = 0; variant_index < capabilities.variantCount; ++variant_index) {
+                const detail::VpVariantDesc& variant = capabilities.pVariants[variant_index];
+                if (pBlockName != nullptr) {
+                    if (strcmp(variant.blockName, pBlockName) != 0) {
+                        continue;
+                    }
+                    result = VK_SUCCESS;
+                }
+
+                for (uint32_t i = 0; i < variant.formatCount; ++i) {
+                    if (std::find(results.begin(), results.end(), variant.pFormats[i].format) == std::end(results)) {
+                        results.push_back(variant.pFormats[i].format);
+                    }
+                }
+            }
+        }
+    }
+
+    const uint32_t count = static_cast<uint32_t>(results.size());
+
+    if (pFormats == nullptr) {
+        *pFormatCount = count;
+    } else {
+        if (*pFormatCount < count) {
+            result = VK_INCOMPLETE;
+        } else {
+            *pFormatCount = count;
+        }
+
+        if (*pFormatCount > 0) {
+            memcpy(pFormats, &results[0], *pFormatCount * sizeof(VkFormat));
+        }
+    }
+    return result;
+}
+
+VPAPI_ATTR VkResult vpGetProfileFormatProperties(const VpProfileProperties *pProfile, const char* pBlockName, VkFormat format, void *pNext) {
+    VkResult result = pBlockName == nullptr ? VK_SUCCESS : VK_INCOMPLETE;
+
+    const std::vector<VpProfileProperties>& profiles = detail::GatherProfiles(*pProfile);
+
+    for (std::size_t i = 0, n = profiles.size(); i < n; ++i) {
+        const char* profile_name = profiles[i].profileName;
+
+        const detail::VpProfileDesc* pProfileDesc = detail::vpGetProfileDesc(profile_name);
+        if (pProfileDesc == nullptr) return VK_ERROR_UNKNOWN;
+
+        for (uint32_t required_capability_index = 0; required_capability_index < pProfileDesc->requiredCapabilityCount;
+                ++required_capability_index) {
+            const detail::VpCapabilitiesDesc& required_capabilities = pProfileDesc->pRequiredCapabilities[required_capability_index];
+
+            for (uint32_t required_variant_index = 0; required_variant_index < required_capabilities.variantCount; ++required_variant_index) {
+                const detail::VpVariantDesc& variant = required_capabilities.pVariants[required_variant_index];
+                if (pBlockName != nullptr) {
+                    if (strcmp(variant.blockName, pBlockName) != 0) {
+                        continue;
+                    }
+                    result = VK_SUCCESS;
+                }
+
+                for (uint32_t i = 0; i < variant.formatCount; ++i) {
+                    if (variant.pFormats[i].format != format) {
+                        continue;
+                    }
+
+                    VkBaseOutStructure* p = static_cast<VkBaseOutStructure*>(static_cast<void*>(pNext));
+                    while (p != nullptr) {
+                        variant.pFormats[i].pfnFiller(p);
+                        p = p->pNext;
+                    }
+#if defined(VK_VERSION_1_3) || defined(VK_KHR_format_feature_flags2)
+                    VkFormatProperties2KHR* fp2 = static_cast<VkFormatProperties2KHR*>(
+                        detail::vpGetStructure(pNext, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR));
+                    VkFormatProperties3KHR* fp3 = static_cast<VkFormatProperties3KHR*>(
+                        detail::vpGetStructure(pNext, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR));
+                    if (fp3 != nullptr) {
+                        VkFormatProperties2KHR fp{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR };
+                        variant.pFormats[i].pfnFiller(static_cast<VkBaseOutStructure*>(static_cast<void*>(&fp)));
+                        fp3->linearTilingFeatures |= static_cast<VkFormatFeatureFlags2KHR>(fp3->linearTilingFeatures | fp.formatProperties.linearTilingFeatures);
+                        fp3->optimalTilingFeatures |= static_cast<VkFormatFeatureFlags2KHR>(fp3->optimalTilingFeatures | fp.formatProperties.optimalTilingFeatures);
+                        fp3->bufferFeatures |= static_cast<VkFormatFeatureFlags2KHR>(fp3->bufferFeatures | fp.formatProperties.bufferFeatures);
+                    }
+                    if (fp2 != nullptr) {
+                        VkFormatProperties3KHR fp{ VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR };
+                        variant.pFormats[i].pfnFiller(static_cast<VkBaseOutStructure*>(static_cast<void*>(&fp)));
+                        fp2->formatProperties.linearTilingFeatures |= static_cast<VkFormatFeatureFlags>(fp2->formatProperties.linearTilingFeatures | fp.linearTilingFeatures);
+                        fp2->formatProperties.optimalTilingFeatures |= static_cast<VkFormatFeatureFlags>(fp2->formatProperties.optimalTilingFeatures | fp.optimalTilingFeatures);
+                        fp2->formatProperties.bufferFeatures |= static_cast<VkFormatFeatureFlags>(fp2->formatProperties.bufferFeatures | fp.bufferFeatures);
+                    }
+#endif
+                }
+            }
+        }
+    }
+
+    return result;
+}
+
+VPAPI_ATTR VkResult vpGetProfileFeatureStructureTypes(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pStructureTypeCount, VkStructureType *pStructureTypes) {
+    return detail::vpGetProfileStructureTypes(pProfile, pBlockName, detail::STRUCTURE_FEATURE, pStructureTypeCount, pStructureTypes);
+}
+
+VPAPI_ATTR VkResult vpGetProfilePropertyStructureTypes(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pStructureTypeCount, VkStructureType *pStructureTypes) {
+    return detail::vpGetProfileStructureTypes(pProfile, pBlockName, detail::STRUCTURE_PROPERTY, pStructureTypeCount, pStructureTypes);
+}
+
+VPAPI_ATTR VkResult vpGetProfileFormatStructureTypes(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pStructureTypeCount, VkStructureType *pStructureTypes) {
+    return detail::vpGetProfileStructureTypes(pProfile, pBlockName, detail::STRUCTURE_FORMAT, pStructureTypeCount, pStructureTypes);
+}
+
+// clang-format on
diff --git a/vulkan/vkprofiles/generated/vulkan_profiles.h b/vulkan/vkprofiles/generated/vulkan_profiles.h
new file mode 100644
index 0000000..4bfb6f6
--- /dev/null
+++ b/vulkan/vkprofiles/generated/vulkan_profiles.h
@@ -0,0 +1,263 @@
+
+/*
+ * Copyright 2024 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.
+ *
+*/
+
+// clang-format off
+
+#ifndef VULKAN_PROFILES_H_
+#define VULKAN_PROFILES_H_ 1
+
+#define VPAPI_ATTR
+
+#ifdef __cplusplus
+    extern "C" {
+#endif
+
+#include <vulkan/vulkan.h>
+
+#if defined(VK_VERSION_1_1) && \
+    defined(VK_ANDROID_external_memory_android_hardware_buffer) && \
+    defined(VK_EXT_queue_family_foreign) && \
+    defined(VK_EXT_swapchain_colorspace) && \
+    defined(VK_GOOGLE_display_timing) && \
+    defined(VK_KHR_android_surface) && \
+    defined(VK_KHR_create_renderpass2) && \
+    defined(VK_KHR_dedicated_allocation) && \
+    defined(VK_KHR_descriptor_update_template) && \
+    defined(VK_KHR_driver_properties) && \
+    defined(VK_KHR_external_fence) && \
+    defined(VK_KHR_external_fence_capabilities) && \
+    defined(VK_KHR_external_fence_fd) && \
+    defined(VK_KHR_external_memory) && \
+    defined(VK_KHR_external_memory_capabilities) && \
+    defined(VK_KHR_external_semaphore) && \
+    defined(VK_KHR_external_semaphore_capabilities) && \
+    defined(VK_KHR_external_semaphore_fd) && \
+    defined(VK_KHR_get_memory_requirements2) && \
+    defined(VK_KHR_get_physical_device_properties2) && \
+    defined(VK_KHR_get_surface_capabilities2) && \
+    defined(VK_KHR_incremental_present) && \
+    defined(VK_KHR_maintenance1) && \
+    defined(VK_KHR_sampler_mirror_clamp_to_edge) && \
+    defined(VK_KHR_storage_buffer_storage_class) && \
+    defined(VK_KHR_surface) && \
+    defined(VK_KHR_swapchain) && \
+    defined(VK_KHR_variable_pointers)
+#define VP_ANDROID_baseline_2022 1
+#define VP_ANDROID_BASELINE_2022_NAME "VP_ANDROID_baseline_2022"
+#define VP_ANDROID_BASELINE_2022_SPEC_VERSION 1
+#define VP_ANDROID_BASELINE_2022_MIN_API_VERSION VK_MAKE_VERSION(1, 1, 106)
+#endif
+
+#if defined(VK_VERSION_1_3) && \
+    defined(VP_ANDROID_baseline_2022) && \
+    defined(VK_ANDROID_external_format_resolve) && \
+    defined(VK_EXT_4444_formats) && \
+    defined(VK_EXT_custom_border_color) && \
+    defined(VK_EXT_device_memory_report) && \
+    defined(VK_EXT_external_memory_acquire_unmodified) && \
+    defined(VK_EXT_index_type_uint8) && \
+    defined(VK_EXT_line_rasterization) && \
+    defined(VK_EXT_load_store_op_none) && \
+    defined(VK_EXT_primitive_topology_list_restart) && \
+    defined(VK_EXT_primitives_generated_query) && \
+    defined(VK_EXT_provoking_vertex) && \
+    defined(VK_EXT_scalar_block_layout) && \
+    defined(VK_EXT_surface_maintenance1) && \
+    defined(VK_EXT_swapchain_maintenance1) && \
+    defined(VK_GOOGLE_surfaceless_query) && \
+    defined(VK_IMG_relaxed_line_rasterization) && \
+    defined(VK_KHR_16bit_storage) && \
+    defined(VK_KHR_maintenance5) && \
+    defined(VK_KHR_shader_float16_int8) && \
+    defined(VK_KHR_vertex_attribute_divisor)
+#define VP_ANDROID_15_minimums 1
+#define VP_ANDROID_15_MINIMUMS_NAME "VP_ANDROID_15_minimums"
+#define VP_ANDROID_15_MINIMUMS_SPEC_VERSION 1
+#define VP_ANDROID_15_MINIMUMS_MIN_API_VERSION VK_MAKE_VERSION(1, 3, 273)
+#endif
+
+#if defined(VK_VERSION_1_0) && \
+    defined(VK_EXT_swapchain_colorspace) && \
+    defined(VK_GOOGLE_display_timing) && \
+    defined(VK_KHR_android_surface) && \
+    defined(VK_KHR_dedicated_allocation) && \
+    defined(VK_KHR_descriptor_update_template) && \
+    defined(VK_KHR_external_fence) && \
+    defined(VK_KHR_external_fence_capabilities) && \
+    defined(VK_KHR_external_fence_fd) && \
+    defined(VK_KHR_external_memory) && \
+    defined(VK_KHR_external_memory_capabilities) && \
+    defined(VK_KHR_external_semaphore) && \
+    defined(VK_KHR_external_semaphore_capabilities) && \
+    defined(VK_KHR_external_semaphore_fd) && \
+    defined(VK_KHR_get_memory_requirements2) && \
+    defined(VK_KHR_get_physical_device_properties2) && \
+    defined(VK_KHR_get_surface_capabilities2) && \
+    defined(VK_KHR_incremental_present) && \
+    defined(VK_KHR_maintenance1) && \
+    defined(VK_KHR_storage_buffer_storage_class) && \
+    defined(VK_KHR_surface) && \
+    defined(VK_KHR_swapchain) && \
+    defined(VK_KHR_variable_pointers)
+#define VP_ANDROID_baseline_2021 1
+#define VP_ANDROID_BASELINE_2021_NAME "VP_ANDROID_baseline_2021"
+#define VP_ANDROID_BASELINE_2021_SPEC_VERSION 2
+#define VP_ANDROID_BASELINE_2021_MIN_API_VERSION VK_MAKE_VERSION(1, 0, 68)
+#endif
+
+#if defined(VK_VERSION_1_0) && \
+    defined(VK_EXT_swapchain_colorspace) && \
+    defined(VK_KHR_android_surface) && \
+    defined(VK_KHR_dedicated_allocation) && \
+    defined(VK_KHR_descriptor_update_template) && \
+    defined(VK_KHR_external_fence) && \
+    defined(VK_KHR_external_fence_capabilities) && \
+    defined(VK_KHR_external_memory) && \
+    defined(VK_KHR_external_memory_capabilities) && \
+    defined(VK_KHR_external_semaphore) && \
+    defined(VK_KHR_external_semaphore_capabilities) && \
+    defined(VK_KHR_external_semaphore_fd) && \
+    defined(VK_KHR_get_memory_requirements2) && \
+    defined(VK_KHR_get_physical_device_properties2) && \
+    defined(VK_KHR_get_surface_capabilities2) && \
+    defined(VK_KHR_incremental_present) && \
+    defined(VK_KHR_maintenance1) && \
+    defined(VK_KHR_storage_buffer_storage_class) && \
+    defined(VK_KHR_surface) && \
+    defined(VK_KHR_swapchain)
+#define VP_ANDROID_baseline_2021_cpu_only 1
+#define VP_ANDROID_BASELINE_2021_CPU_ONLY_NAME "VP_ANDROID_baseline_2021_cpu_only"
+#define VP_ANDROID_BASELINE_2021_CPU_ONLY_SPEC_VERSION 1
+#define VP_ANDROID_BASELINE_2021_CPU_ONLY_MIN_API_VERSION VK_MAKE_VERSION(1, 0, 68)
+#endif
+
+#define VP_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(0, 2, 0, VK_HEADER_VERSION)
+
+#define VP_MAX_PROFILE_NAME_SIZE 256U
+
+typedef struct VpProfileProperties {
+    char        profileName[VP_MAX_PROFILE_NAME_SIZE];
+    uint32_t    specVersion;
+} VpProfileProperties;
+
+typedef struct VpBlockProperties {
+    VpProfileProperties profiles;
+    uint32_t apiVersion;
+    char blockName[VP_MAX_PROFILE_NAME_SIZE];
+} VpBlockProperties;
+
+typedef enum VpInstanceCreateFlagBits {
+    VP_INSTANCE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VpInstanceCreateFlagBits;
+typedef VkFlags VpInstanceCreateFlags;
+
+typedef struct VpInstanceCreateInfo {
+    const VkInstanceCreateInfo* pCreateInfo;
+    VpInstanceCreateFlags       flags;
+    uint32_t                    enabledFullProfileCount;
+    const VpProfileProperties*  pEnabledFullProfiles;
+    uint32_t                    enabledProfileBlockCount;
+    const VpBlockProperties*    pEnabledProfileBlocks;
+} VpInstanceCreateInfo;
+
+typedef enum VpDeviceCreateFlagBits {
+    VP_DEVICE_CREATE_DISABLE_ROBUST_BUFFER_ACCESS_BIT = 0x0000001,
+    VP_DEVICE_CREATE_DISABLE_ROBUST_IMAGE_ACCESS_BIT = 0x0000002,
+    VP_DEVICE_CREATE_DISABLE_ROBUST_ACCESS =
+        VP_DEVICE_CREATE_DISABLE_ROBUST_BUFFER_ACCESS_BIT | VP_DEVICE_CREATE_DISABLE_ROBUST_IMAGE_ACCESS_BIT,
+
+    VP_DEVICE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VpDeviceCreateFlagBits;
+typedef VkFlags VpDeviceCreateFlags;
+
+typedef struct VpDeviceCreateInfo {
+    const VkDeviceCreateInfo*   pCreateInfo;
+    VpDeviceCreateFlags         flags;
+    uint32_t                    enabledFullProfileCount;
+    const VpProfileProperties*  pEnabledFullProfiles;
+    uint32_t                    enabledProfileBlockCount;
+    const VpBlockProperties*    pEnabledProfileBlocks;
+} VpDeviceCreateInfo;
+
+// Query the list of available profiles in the library
+VPAPI_ATTR VkResult vpGetProfiles(uint32_t *pPropertyCount, VpProfileProperties *pProperties);
+
+// List the required profiles of a profile
+VPAPI_ATTR VkResult vpGetProfileRequiredProfiles(const VpProfileProperties* pProfile, uint32_t* pPropertyCount, VpProfileProperties* pProperties);
+
+// Query the profile required Vulkan API version
+VPAPI_ATTR uint32_t vpGetProfileAPIVersion(const VpProfileProperties* pProfile);
+
+// List the recommended fallback profiles of a profile
+VPAPI_ATTR VkResult vpGetProfileFallbacks(const VpProfileProperties *pProfile, uint32_t *pPropertyCount, VpProfileProperties *pProperties);
+
+// Query whether the profile has multiple variants. Profiles with multiple variants can only use vpGetInstanceProfileSupport and vpGetPhysicalDeviceProfileSupport capabilities of the library. Other function will return a VK_ERROR_UNKNOWN error
+VPAPI_ATTR VkResult vpHasMultipleVariantsProfile(const VpProfileProperties *pProfile, VkBool32 *pHasMultipleVariants);
+
+// Check whether a profile is supported at the instance level
+VPAPI_ATTR VkResult vpGetInstanceProfileSupport(const char *pLayerName, const VpProfileProperties *pProfile, VkBool32 *pSupported);
+
+// Check whether a variant of a profile is supported at the instance level and report this list of blocks used to validate the profiles
+VPAPI_ATTR VkResult vpGetInstanceProfileVariantsSupport(const char *pLayerName, const VpProfileProperties *pProfile, VkBool32 *pSupported, uint32_t *pPropertyCount, VpBlockProperties* pProperties);
+
+// Create a VkInstance with the profile instance extensions enabled
+VPAPI_ATTR VkResult vpCreateInstance(const VpInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance);
+
+// Check whether a profile is supported by the physical device
+VPAPI_ATTR VkResult vpGetPhysicalDeviceProfileSupport(VkInstance instance, VkPhysicalDevice physicalDevice, const VpProfileProperties *pProfile, VkBool32 *pSupported);
+
+// Check whether a variant of a profile is supported by the physical device and report this list of blocks used to validate the profiles
+VPAPI_ATTR VkResult vpGetPhysicalDeviceProfileVariantsSupport(VkInstance instance, VkPhysicalDevice physicalDevice, const VpProfileProperties *pProfile, VkBool32 *pSupported, uint32_t *pPropertyCount, VpBlockProperties* pProperties);
+
+// Create a VkDevice with the profile features and device extensions enabled
+VPAPI_ATTR VkResult vpCreateDevice(VkPhysicalDevice physicalDevice, const VpDeviceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDevice *pDevice);
+
+// Query the list of instance extensions of a profile
+VPAPI_ATTR VkResult vpGetProfileInstanceExtensionProperties(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties);
+
+// Query the list of device extensions of a profile
+VPAPI_ATTR VkResult vpGetProfileDeviceExtensionProperties(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties);
+
+// Fill the feature structures with the requirements of a profile
+VPAPI_ATTR VkResult vpGetProfileFeatures(const VpProfileProperties *pProfile, const char* pBlockName, void *pNext);
+
+// Query the list of feature structure types specified by the profile
+VPAPI_ATTR VkResult vpGetProfileFeatureStructureTypes(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pStructureTypeCount, VkStructureType *pStructureTypes);
+
+// Fill the property structures with the requirements of a profile
+VPAPI_ATTR VkResult vpGetProfileProperties(const VpProfileProperties *pProfile, const char* pBlockName, void *pNext);
+
+// Query the list of property structure types specified by the profile
+VPAPI_ATTR VkResult vpGetProfilePropertyStructureTypes(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pStructureTypeCount, VkStructureType *pStructureTypes);
+
+// Query the list of formats with specified requirements by a profile
+VPAPI_ATTR VkResult vpGetProfileFormats(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pFormatCount, VkFormat *pFormats);
+
+// Query the requirements of a format for a profile
+VPAPI_ATTR VkResult vpGetProfileFormatProperties(const VpProfileProperties *pProfile, const char* pBlockName, VkFormat format, void *pNext);
+
+// Query the list of format structure types specified by the profile
+VPAPI_ATTR VkResult vpGetProfileFormatStructureTypes(const VpProfileProperties *pProfile, const char* pBlockName, uint32_t *pStructureTypeCount, VkStructureType *pStructureTypes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VULKAN_PROFILES_H_
+
+// clang-format on
diff --git a/vulkan/vkprofiles/profiles/VP_ANDROID_15_minimums.json b/vulkan/vkprofiles/profiles/VP_ANDROID_15_minimums.json
new file mode 100644
index 0000000..9e17253
--- /dev/null
+++ b/vulkan/vkprofiles/profiles/VP_ANDROID_15_minimums.json
@@ -0,0 +1,174 @@
+{
+    "$schema": "https://schema.khronos.org/vulkan/profiles-0.8.2-273.json#",
+    "capabilities": {
+        "MUST": {
+            "extensions": {
+                "VK_KHR_maintenance5": 1,
+                "VK_KHR_shader_float16_int8": 1,
+                "VK_KHR_16bit_storage": 1,
+                "VK_KHR_vertex_attribute_divisor": 1,
+                "VK_EXT_custom_border_color": 1,
+                "VK_EXT_device_memory_report": 1,
+                "VK_EXT_external_memory_acquire_unmodified": 1,
+                "VK_EXT_index_type_uint8": 1,
+                "VK_EXT_load_store_op_none": 1,
+                "VK_EXT_primitive_topology_list_restart": 1,
+                "VK_EXT_provoking_vertex": 1,
+                "VK_EXT_scalar_block_layout": 1,
+                "VK_EXT_surface_maintenance1": 1,
+                "VK_EXT_swapchain_maintenance1": 1,
+                "VK_EXT_4444_formats": 1,
+                "VK_ANDROID_external_format_resolve": 1,
+                "VK_GOOGLE_surfaceless_query": 1
+            },
+            "features": {
+                "VkPhysicalDeviceFeatures": {
+                    "drawIndirectFirstInstance": true,
+                    "shaderImageGatherExtended": true,
+                    "shaderStorageImageExtendedFormats": true,
+                    "shaderStorageImageReadWithoutFormat": true,
+                    "shaderStorageImageWriteWithoutFormat": true,
+                    "samplerAnisotropy": true
+                },
+                "VkPhysicalDeviceVulkan12Features": {
+                    "shaderFloat16": true,
+                    "shaderInt8": true
+                },
+                "VkPhysicalDeviceCustomBorderColorFeaturesEXT": {
+                    "customBorderColors": true
+                },
+                "VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT": {
+                    "primitiveTopologyListRestart": true
+                },
+                "VkPhysicalDeviceProvokingVertexFeaturesEXT": {
+                    "provokingVertexLast": true
+                },
+                "VkPhysicalDeviceIndexTypeUint8FeaturesEXT": {
+                    "indexTypeUint8": true
+                },
+                "VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR": {
+                    "vertexAttributeInstanceRateDivisor": true
+                },
+                "VkPhysicalDeviceSamplerYcbcrConversionFeatures": {
+                    "samplerYcbcrConversion": true
+                },
+                "VkPhysicalDeviceShaderFloat16Int8Features": {
+                    "shaderFloat16": true,
+                    "shaderInt8": true
+                },
+                "VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures": {
+                    "shaderSubgroupExtendedTypes": true
+                },
+                "VkPhysicalDevice8BitStorageFeatures": {
+                    "storageBuffer8BitAccess": true
+                },
+                "VkPhysicalDevice16BitStorageFeatures": {
+                    "storageBuffer16BitAccess": true
+                }
+            },
+            "properties": {
+                "VkPhysicalDeviceProperties": {
+                    "limits": {
+                        "maxPerStageDescriptorUniformBuffers": 13,
+                        "maxPerStageDescriptorStorageBuffers": 12,
+                        "maxColorAttachments": 8,
+                        "maxPerStageDescriptorSampledImages": 128,
+                        "maxPerStageDescriptorSamplers": 128
+                    }
+                },
+                "VkPhysicalDeviceVulkan11Properties": {
+                  "subgroupSupportedOperations": ["VK_SUBGROUP_FEATURE_BASIC_BIT", "VK_SUBGROUP_FEATURE_VOTE_BIT", "VK_SUBGROUP_FEATURE_ARITHMETIC_BIT", "VK_SUBGROUP_FEATURE_BALLOT_BIT", "VK_SUBGROUP_FEATURE_SHUFFLE_BIT", "VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT"]
+                }
+            },
+            "formats": {
+                "VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                }
+            }
+        },
+        "primitivesGeneratedQuery": {
+            "extensions": {
+                "VK_EXT_primitives_generated_query": 1
+            },
+            "features": {
+                "VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT": {
+                    "primitivesGeneratedQuery": true
+                }
+            }
+        },
+        "pipelineStatisticsQuery": {
+            "features": {
+                "VkPhysicalDeviceFeatures": {
+                    "pipelineStatisticsQuery": true
+                }
+            }
+        },
+        "swBresenhamLines": {
+            "extensions": {
+                "VK_EXT_line_rasterization": 1
+            },
+            "features": {
+                "VkPhysicalDeviceLineRasterizationFeaturesEXT": {
+                    "bresenhamLines": true
+                }
+            }
+        },
+        "hwBresenhamLines": {
+            "extensions": {
+                "VK_IMG_relaxed_line_rasterization": 1
+            },
+            "features": {
+                "VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG": {
+                    "relaxedLineRasterization": true
+                }
+            }
+        }
+    },
+    "profiles": {
+        "VP_ANDROID_15_minimums": {
+            "version": 1,
+            "api-version": "1.3.273",
+            "label": "Vulkan Minimum Requirements for Android 15",
+            "description": "Collection of functionality that is mandated for chipsets that launch (or renew Google Requirements Freeze) on Android 15",
+            "contributors": {
+                "Trevor David Black": {
+                    "company": "Google",
+                    "email": "vantablack@google.com",
+                    "contact": true
+                },
+                "Ian Elliott": {
+                    "company": "Google",
+                    "email": "ianelliott@google.com",
+                    "contact": true
+                }
+            },
+            "history": [
+                {
+                    "revision": 1,
+                    "date": "2023-12-15",
+                    "author": "Ian Elliott",
+                    "comment": "First version"
+                 }
+            ],
+            "profiles": [
+                "VP_ANDROID_baseline_2022"
+             ],
+             "capabilities": [
+                "MUST",
+                ["primitivesGeneratedQuery", "pipelineStatisticsQuery"],
+                ["swBresenhamLines", "hwBresenhamLines"]
+            ]
+        }
+    }
+}
diff --git a/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2021.json b/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2021.json
new file mode 100644
index 0000000..1a437fb
--- /dev/null
+++ b/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2021.json
@@ -0,0 +1,794 @@
+{
+    "$schema": "https://schema.khronos.org/vulkan/profiles-0.8.0-106.json#",
+    "capabilities": {
+        "baseline": {
+            "extensions": {
+                "VK_KHR_surface": 1,
+                "VK_KHR_android_surface": 1,
+                "VK_KHR_swapchain": 1,
+                "VK_KHR_get_physical_device_properties2": 1,
+                "VK_KHR_maintenance1": 1,
+                "VK_EXT_swapchain_colorspace": 1,
+                "VK_KHR_get_surface_capabilities2": 1,
+                "VK_KHR_incremental_present": 1,
+                "VK_GOOGLE_display_timing": 1,
+                "VK_KHR_descriptor_update_template": 1,
+                "VK_KHR_get_memory_requirements2": 1,
+                "VK_KHR_dedicated_allocation": 1,
+                "VK_KHR_storage_buffer_storage_class": 1,
+                "VK_KHR_external_semaphore_capabilities": 1,
+                "VK_KHR_external_semaphore": 1,
+                "VK_KHR_external_memory_capabilities": 1,
+                "VK_KHR_external_memory": 1,
+                "VK_KHR_external_fence_capabilities": 1,
+                "VK_KHR_external_semaphore_fd": 1,
+                "VK_KHR_external_fence": 1,
+                "VK_KHR_external_fence_fd": 1,
+                "VK_KHR_variable_pointers": 1
+            },
+            "features": {
+                "VkPhysicalDeviceFeatures": {
+                    "depthBiasClamp": true,
+                    "fragmentStoresAndAtomics": true,
+                    "fullDrawIndexUint32": true,
+                    "imageCubeArray": true,
+                    "independentBlend": true,
+                    "robustBufferAccess": true,
+                    "sampleRateShading": true,
+                    "shaderSampledImageArrayDynamicIndexing": true,
+                    "shaderStorageImageArrayDynamicIndexing": true,
+                    "shaderUniformBufferArrayDynamicIndexing": true,
+                    "textureCompressionASTC_LDR": true,
+                    "textureCompressionETC2": true
+                }
+            },
+            "properties": {
+                "VkPhysicalDeviceProperties": {
+                    "limits": {
+                        "maxImageDimension1D": 4096,
+                        "maxImageDimension2D": 4096,
+                        "maxImageDimension3D": 512,
+                        "maxImageDimensionCube": 4096,
+                        "maxImageArrayLayers": 256,
+                        "maxTexelBufferElements": 65536,
+                        "maxUniformBufferRange": 16384,
+                        "maxStorageBufferRange": 134217728,
+                        "maxPushConstantsSize": 128,
+                        "maxMemoryAllocationCount": 4096,
+                        "maxSamplerAllocationCount": 4000,
+                        "maxBoundDescriptorSets": 4,
+                        "maxPerStageDescriptorSamplers": 16,
+                        "maxPerStageDescriptorUniformBuffers": 12,
+                        "maxPerStageDescriptorStorageBuffers": 4,
+                        "maxPerStageDescriptorSampledImages": 16,
+                        "maxPerStageDescriptorStorageImages": 4,
+                        "maxPerStageDescriptorInputAttachments": 4,
+                        "maxPerStageResources": 44,
+                        "maxDescriptorSetSamplers": 48,
+                        "maxDescriptorSetUniformBuffers": 36,
+                        "maxDescriptorSetUniformBuffersDynamic": 8,
+                        "maxDescriptorSetStorageBuffers": 24,
+                        "maxDescriptorSetStorageBuffersDynamic": 4,
+                        "maxDescriptorSetSampledImages": 48,
+                        "maxDescriptorSetStorageImages": 12,
+                        "maxDescriptorSetInputAttachments": 4,
+                        "maxVertexInputAttributes": 16,
+                        "maxVertexInputBindings": 16,
+                        "maxVertexInputAttributeOffset": 2047,
+                        "maxVertexInputBindingStride": 2048,
+                        "maxVertexOutputComponents": 64,
+                        "maxFragmentInputComponents": 64,
+                        "maxFragmentOutputAttachments": 4,
+                        "maxFragmentCombinedOutputResources": 8,
+                        "maxComputeSharedMemorySize": 16384,
+                        "maxComputeWorkGroupCount": [ 65535, 65535, 65535 ],
+                        "maxComputeWorkGroupInvocations": 128,
+                        "maxComputeWorkGroupSize": [ 128, 128, 64 ],
+                        "subPixelPrecisionBits": 4,
+                        "subTexelPrecisionBits": 4,
+                        "mipmapPrecisionBits": 4,
+                        "maxDrawIndexedIndexValue": 4294967295,
+                        "maxDrawIndirectCount": 1,
+                        "maxSamplerLodBias": 2.0,
+                        "maxSamplerAnisotropy": 1.0,
+                        "maxViewports": 1,
+                        "maxViewportDimensions": [ 4096, 4096 ],
+                        "viewportBoundsRange": [ -8192, 8191 ],
+                        "minMemoryMapAlignment": 4096,
+                        "minTexelBufferOffsetAlignment": 256,
+                        "minUniformBufferOffsetAlignment": 256,
+                        "minStorageBufferOffsetAlignment": 256,
+                        "minTexelOffset": -8,
+                        "maxTexelOffset": 7,
+                        "minInterpolationOffset": -0.5,
+                        "maxInterpolationOffset": 0.4375,
+                        "subPixelInterpolationOffsetBits": 4,
+                        "maxFramebufferWidth": 4096,
+                        "maxFramebufferHeight": 4096,
+                        "maxFramebufferLayers": 256,
+                        "framebufferColorSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferDepthSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferStencilSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferNoAttachmentsSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "maxColorAttachments": 4,
+                        "sampledImageColorSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "sampledImageIntegerSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT" ],
+                        "sampledImageDepthSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "sampledImageStencilSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "storageImageSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT" ],
+                        "maxSampleMaskWords": 1,
+                        "discreteQueuePriorities": 2,
+                        "pointSizeGranularity": 1,
+                        "standardSampleLocations": true
+                    }
+                }
+            },
+            "formats": {
+                "VK_FORMAT_ASTC_4x4_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_4x4_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x4_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x4_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x10_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x10_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x10_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x10_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x12_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x12_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B4G4R4A4_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R5G6B5_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A1R5G5B5_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SRGB": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B8G8R8A8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_B8G8R8A8_SRGB": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_UNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_UINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SRGB_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A2B10G10R10_UNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A2B10G10R10_UINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R16G16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32G32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32G32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32G32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B10G11R11_UFLOAT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_E5B9G9R9_UFLOAT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_D16_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_D32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11_SNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11G11_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11G11_SNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                }
+            }
+        }
+    },
+    "profiles": {
+        "VP_ANDROID_baseline_2021": {
+            "version": 2,
+            "api-version": "1.0.68",
+            "label": "Android Vulkan Baseline 2021 profile",
+            "description": "Collection of functionality that is broadly supported on Android",
+            "contributors": {
+                "Trevor David Black": {
+                    "company": "Google",
+                    "email": "vantablack@google.com",
+                    "contact": true
+                },
+                "Ian Elliott": {
+                    "company": "Google",
+                    "email": "ianelliott@google.com",
+                    "contact": true
+                },
+                "Frank Yang": {
+                    "company": "Google",
+                    "contact": false
+                }
+            },
+            "history": [
+                {
+                    "revision": 2,
+                    "date": "2023-01-10",
+                    "author": "Trevor David Black",
+                    "comment": "Remove shaderImageGatherExtended and limits"
+                },
+                {
+                    "revision": 1,
+                    "date": "2022-01-11",
+                    "author": "Trevor David Black",
+                    "comment": "Final draft"
+                }
+            ],
+            "capabilities": [
+                "baseline"
+            ]
+        }
+    }
+}
diff --git a/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2021_cpu_only.json b/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2021_cpu_only.json
new file mode 100644
index 0000000..7ce337c
--- /dev/null
+++ b/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2021_cpu_only.json
@@ -0,0 +1,780 @@
+{
+    "$schema": "https://schema.khronos.org/vulkan/profiles-0.8.0-106.json#",
+    "capabilities": {
+        "baseline": {
+            "extensions": {
+                "VK_KHR_surface": 1,
+                "VK_KHR_android_surface": 1,
+                "VK_KHR_swapchain": 1,
+                "VK_KHR_get_physical_device_properties2": 1,
+                "VK_KHR_maintenance1": 1,
+                "VK_EXT_swapchain_colorspace": 1,
+                "VK_KHR_get_surface_capabilities2": 1,
+                "VK_KHR_incremental_present": 1,
+                "VK_KHR_descriptor_update_template": 1,
+                "VK_KHR_get_memory_requirements2": 1,
+                "VK_KHR_dedicated_allocation": 1,
+                "VK_KHR_storage_buffer_storage_class": 1,
+                "VK_KHR_external_semaphore_capabilities": 1,
+                "VK_KHR_external_semaphore": 1,
+                "VK_KHR_external_memory_capabilities": 1,
+                "VK_KHR_external_memory": 1,
+                "VK_KHR_external_fence_capabilities": 1,
+                "VK_KHR_external_semaphore_fd": 1,
+                "VK_KHR_external_fence": 1
+            },
+            "features": {
+                "VkPhysicalDeviceFeatures": {
+                    "depthBiasClamp": true,
+                    "fragmentStoresAndAtomics": true,
+                    "fullDrawIndexUint32": true,
+                    "imageCubeArray": true,
+                    "independentBlend": true,
+                    "robustBufferAccess": true,
+                    "sampleRateShading": true,
+                    "shaderSampledImageArrayDynamicIndexing": true,
+                    "shaderStorageImageArrayDynamicIndexing": true,
+                    "shaderUniformBufferArrayDynamicIndexing": true,
+                    "textureCompressionASTC_LDR": true,
+                    "textureCompressionETC2": true
+                }
+            },
+            "properties": {
+                "VkPhysicalDeviceProperties": {
+                    "limits": {
+                        "maxImageDimension1D": 4096,
+                        "maxImageDimension2D": 4096,
+                        "maxImageDimension3D": 512,
+                        "maxImageDimensionCube": 4096,
+                        "maxImageArrayLayers": 256,
+                        "maxTexelBufferElements": 65536,
+                        "maxUniformBufferRange": 16384,
+                        "maxStorageBufferRange": 134217728,
+                        "maxPushConstantsSize": 128,
+                        "maxMemoryAllocationCount": 4096,
+                        "maxSamplerAllocationCount": 4000,
+                        "maxBoundDescriptorSets": 4,
+                        "maxPerStageDescriptorSamplers": 16,
+                        "maxPerStageDescriptorUniformBuffers": 12,
+                        "maxPerStageDescriptorStorageBuffers": 4,
+                        "maxPerStageDescriptorSampledImages": 16,
+                        "maxPerStageDescriptorStorageImages": 4,
+                        "maxPerStageDescriptorInputAttachments": 4,
+                        "maxPerStageResources": 44,
+                        "maxDescriptorSetSamplers": 48,
+                        "maxDescriptorSetUniformBuffers": 36,
+                        "maxDescriptorSetUniformBuffersDynamic": 8,
+                        "maxDescriptorSetStorageBuffers": 24,
+                        "maxDescriptorSetStorageBuffersDynamic": 4,
+                        "maxDescriptorSetSampledImages": 48,
+                        "maxDescriptorSetStorageImages": 12,
+                        "maxDescriptorSetInputAttachments": 4,
+                        "maxVertexInputAttributes": 16,
+                        "maxVertexInputBindings": 16,
+                        "maxVertexInputAttributeOffset": 2047,
+                        "maxVertexInputBindingStride": 2048,
+                        "maxVertexOutputComponents": 64,
+                        "maxFragmentInputComponents": 64,
+                        "maxFragmentOutputAttachments": 4,
+                        "maxFragmentCombinedOutputResources": 8,
+                        "maxComputeSharedMemorySize": 16384,
+                        "maxComputeWorkGroupCount": [ 65535, 65535, 65535 ],
+                        "maxComputeWorkGroupInvocations": 128,
+                        "maxComputeWorkGroupSize": [ 128, 128, 64 ],
+                        "subPixelPrecisionBits": 4,
+                        "subTexelPrecisionBits": 4,
+                        "mipmapPrecisionBits": 4,
+                        "maxDrawIndexedIndexValue": 4294967295,
+                        "maxDrawIndirectCount": 1,
+                        "maxSamplerLodBias": 2.0,
+                        "maxSamplerAnisotropy": 1.0,
+                        "maxViewports": 1,
+                        "maxViewportDimensions": [ 4096, 4096 ],
+                        "viewportBoundsRange": [ -8192, 8191 ],
+                        "minMemoryMapAlignment": 4096,
+                        "minTexelBufferOffsetAlignment": 256,
+                        "minUniformBufferOffsetAlignment": 256,
+                        "minStorageBufferOffsetAlignment": 256,
+                        "minTexelOffset": -8,
+                        "maxTexelOffset": 7,
+                        "minInterpolationOffset": -0.5,
+                        "maxInterpolationOffset": 0.4375,
+                        "subPixelInterpolationOffsetBits": 4,
+                        "maxFramebufferWidth": 4096,
+                        "maxFramebufferHeight": 4096,
+                        "maxFramebufferLayers": 256,
+                        "framebufferColorSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferDepthSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferStencilSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferNoAttachmentsSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "maxColorAttachments": 4,
+                        "sampledImageColorSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "sampledImageIntegerSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT" ],
+                        "sampledImageDepthSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "sampledImageStencilSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "storageImageSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT" ],
+                        "maxSampleMaskWords": 1,
+                        "discreteQueuePriorities": 2,
+                        "standardSampleLocations": true
+                    }
+                }
+            },
+            "formats": {
+                "VK_FORMAT_ASTC_4x4_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_4x4_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x4_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x4_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x10_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x10_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x10_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x10_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x12_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x12_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B4G4R4A4_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R5G6B5_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A1R5G5B5_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SRGB": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B8G8R8A8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_B8G8R8A8_SRGB": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_UNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_UINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SRGB_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A2B10G10R10_UNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_A2B10G10R10_UINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R16G16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32G32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32G32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32G32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B10G11R11_UFLOAT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": [ "VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT" ]
+                    }
+                },
+                "VK_FORMAT_E5B9G9R9_UFLOAT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_D16_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_D32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11_SNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11G11_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11G11_SNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [ "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "optimalTilingFeatures": [ "VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT" ],
+                        "bufferFeatures": []
+                    }
+                }
+            }
+        }
+    },
+    "profiles": {
+        "VP_ANDROID_baseline_2021_cpu_only": {
+            "version": 1,
+            "api-version": "1.0.68",
+            "label": "Android Vulkan Baseline 2021 cpu only profile ",
+            "description": "Collection of functionality that is broadly supported on CPU only Android",
+            "contributors": {
+                "Trevor David Black": {
+                    "company": "Google",
+                    "email": "vantablack@google.com",
+                    "contact": true
+                },
+                "Ian Elliott": {
+                    "company": "Google",
+                    "email": "ianelliott@google.com",
+                    "contact": true
+                }
+            },
+            "history": [
+                {
+                    "revision": 1,
+                    "date": "2023-03-01",
+                    "author": "Trevor David Black",
+                    "comment": "Final draft"
+                }
+            ],
+            "capabilities": [
+                "baseline"
+            ]
+        }
+    }
+}
diff --git a/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2022.json b/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2022.json
new file mode 100644
index 0000000..bb3f6a6
--- /dev/null
+++ b/vulkan/vkprofiles/profiles/VP_ANDROID_baseline_2022.json
@@ -0,0 +1,810 @@
+{
+    "$schema": "https://schema.khronos.org/vulkan/profiles-0.8.0-106.json#",
+    "capabilities": {
+        "baseline": {
+            "extensions": {
+                "VK_KHR_surface": 1,
+                "VK_KHR_android_surface": 1,
+                "VK_KHR_swapchain": 1,
+                "VK_KHR_get_physical_device_properties2": 1,
+                "VK_KHR_maintenance1": 1,
+                "VK_EXT_swapchain_colorspace": 1,
+                "VK_KHR_get_surface_capabilities2": 1,
+                "VK_KHR_incremental_present": 1,
+                "VK_GOOGLE_display_timing": 1,
+                "VK_KHR_descriptor_update_template": 1,
+                "VK_KHR_get_memory_requirements2": 1,
+                "VK_KHR_dedicated_allocation": 1,
+                "VK_KHR_storage_buffer_storage_class": 1,
+                "VK_KHR_external_semaphore_capabilities": 1,
+                "VK_KHR_external_semaphore": 1,
+                "VK_KHR_external_memory_capabilities": 1,
+                "VK_KHR_external_memory": 1,
+                "VK_KHR_external_fence_capabilities": 1,
+                "VK_KHR_external_semaphore_fd": 1,
+                "VK_KHR_external_fence": 1,
+                "VK_KHR_external_fence_fd": 1,
+                "VK_KHR_variable_pointers": 1,
+                "VK_ANDROID_external_memory_android_hardware_buffer": 1,
+                "VK_EXT_queue_family_foreign": 1,
+                "VK_KHR_driver_properties": 1,
+                "VK_KHR_create_renderpass2": 1,
+                "VK_KHR_sampler_mirror_clamp_to_edge": 1
+            },
+            "features": {
+                "VkPhysicalDeviceFeatures": {
+                    "depthBiasClamp": true,
+                    "fragmentStoresAndAtomics": true,
+                    "fullDrawIndexUint32": true,
+                    "imageCubeArray": true,
+                    "independentBlend": true,
+                    "robustBufferAccess": true,
+                    "sampleRateShading": true,
+                    "shaderSampledImageArrayDynamicIndexing": true,
+                    "shaderStorageImageArrayDynamicIndexing": true,
+                    "shaderUniformBufferArrayDynamicIndexing": true,
+                    "textureCompressionASTC_LDR": true,
+                    "textureCompressionETC2": true,
+                    "shaderInt16": true,
+                    "shaderStorageBufferArrayDynamicIndexing": true,
+                    "largePoints": true
+                },
+                "VkPhysicalDeviceMultiviewFeatures": {
+                    "multiview": true
+                },
+                "VkPhysicalDeviceSamplerYcbcrConversionFeatures": {
+                    "samplerYcbcrConversion": true
+                },
+                "VkPhysicalDeviceShaderDrawParametersFeatures": {
+                    "shaderDrawParameters": true
+                },
+                "VkPhysicalDeviceVariablePointersFeatures": {
+                    "variablePointers": true,
+                    "variablePointersStorageBuffer": true
+                }
+            },
+            "properties": {
+                "VkPhysicalDeviceProperties": {
+                    "limits": {
+                        "maxImageDimension1D": 4096,
+                        "maxImageDimension2D": 4096,
+                        "maxImageDimension3D": 512,
+                        "maxImageDimensionCube": 4096,
+                        "maxImageArrayLayers": 256,
+                        "maxTexelBufferElements": 65536,
+                        "maxUniformBufferRange": 16384,
+                        "maxStorageBufferRange": 134217728,
+                        "maxPushConstantsSize": 128,
+                        "maxMemoryAllocationCount": 4096,
+                        "maxSamplerAllocationCount": 4000,
+                        "maxBoundDescriptorSets": 4,
+                        "maxPerStageDescriptorSamplers": 16,
+                        "maxPerStageDescriptorUniformBuffers": 12,
+                        "maxPerStageDescriptorStorageBuffers": 4,
+                        "maxPerStageDescriptorSampledImages": 16,
+                        "maxPerStageDescriptorStorageImages": 4,
+                        "maxPerStageDescriptorInputAttachments": 4,
+                        "maxPerStageResources": 44,
+                        "maxDescriptorSetSamplers": 48,
+                        "maxDescriptorSetUniformBuffers": 36,
+                        "maxDescriptorSetUniformBuffersDynamic": 8,
+                        "maxDescriptorSetStorageBuffers": 24,
+                        "maxDescriptorSetStorageBuffersDynamic": 4,
+                        "maxDescriptorSetSampledImages": 48,
+                        "maxDescriptorSetStorageImages": 12,
+                        "maxDescriptorSetInputAttachments": 4,
+                        "maxVertexInputAttributes": 16,
+                        "maxVertexInputBindings": 16,
+                        "maxVertexInputAttributeOffset": 2047,
+                        "maxVertexInputBindingStride": 2048,
+                        "maxVertexOutputComponents": 64,
+                        "maxFragmentInputComponents": 64,
+                        "maxFragmentOutputAttachments": 4,
+                        "maxFragmentCombinedOutputResources": 8,
+                        "maxComputeSharedMemorySize": 16384,
+                        "maxComputeWorkGroupCount": [ 65535, 65535, 65535 ],
+                        "maxComputeWorkGroupInvocations": 128,
+                        "maxComputeWorkGroupSize": [ 128, 128, 64 ],
+                        "subPixelPrecisionBits": 4,
+                        "subTexelPrecisionBits": 4,
+                        "mipmapPrecisionBits": 4,
+                        "maxDrawIndexedIndexValue": 4294967295,
+                        "maxDrawIndirectCount": 1,
+                        "maxSamplerLodBias": 2.0,
+                        "maxSamplerAnisotropy": 1.0,
+                        "maxViewports": 1,
+                        "maxViewportDimensions": [ 4096, 4096 ],
+                        "viewportBoundsRange": [ -8192, 8191 ],
+                        "minMemoryMapAlignment": 4096,
+                        "minTexelBufferOffsetAlignment": 256,
+                        "minUniformBufferOffsetAlignment": 256,
+                        "minStorageBufferOffsetAlignment": 256,
+                        "minTexelOffset": -8,
+                        "maxTexelOffset": 7,
+                        "minInterpolationOffset": -0.5,
+                        "maxInterpolationOffset": 0.4375,
+                        "subPixelInterpolationOffsetBits": 4,
+                        "maxFramebufferWidth": 4096,
+                        "maxFramebufferHeight": 4096,
+                        "maxFramebufferLayers": 256,
+                        "framebufferColorSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferDepthSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferStencilSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "framebufferNoAttachmentsSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "maxColorAttachments": 4,
+                        "sampledImageColorSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "sampledImageIntegerSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT" ],
+                        "sampledImageDepthSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "sampledImageStencilSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT", "VK_SAMPLE_COUNT_4_BIT" ],
+                        "storageImageSampleCounts": [ "VK_SAMPLE_COUNT_1_BIT" ],
+                        "maxSampleMaskWords": 1,
+                        "discreteQueuePriorities": 2,
+                        "pointSizeGranularity": 1,
+                        "pointSizeRange": [1.0, 511],
+                        "standardSampleLocations": true
+                    }
+                },
+                "VkPhysicalDeviceMultiviewProperties": {
+                    "maxMultiviewInstanceIndex": 134217727,
+                    "maxMultiviewViewCount": 6
+                }
+            },
+            "formats": {
+                "VK_FORMAT_ASTC_4x4_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_4x4_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x4_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x4_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_5x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_6x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_8x8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x5_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x5_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x6_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x6_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x10_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_10x10_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x10_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x10_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x12_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ASTC_12x12_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B4G4R4A4_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R5G6B5_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A1R5G5B5_UNORM_PACK16": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R8G8B8A8_SRGB": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B8G8R8A8_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_B8G8R8A8_SRGB": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_UNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_UINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_A8B8G8R8_SRGB_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_A2B10G10R10_UNORM_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_A2B10G10R10_UINT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16G16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16G16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R16G16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16G16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": [],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R16G16B16A16_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R32G32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R32G32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R32G32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT", "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_UINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_SINT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_R32G32B32A32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT", "VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_BLIT_DST_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_B10G11R11_UFLOAT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": ["VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT"]
+                    }
+                },
+                "VK_FORMAT_E5B9G9R9_UFLOAT_PACK32": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_D16_UNORM": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_D32_SFLOAT": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": [],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11_SNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11G11_UNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                },
+                "VK_FORMAT_EAC_R11G11_SNORM_BLOCK": {
+                    "VkFormatProperties": {
+                        "linearTilingFeatures": ["VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "optimalTilingFeatures": ["VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT", "VK_FORMAT_FEATURE_BLIT_SRC_BIT", "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT", "VK_FORMAT_FEATURE_TRANSFER_SRC_BIT", "VK_FORMAT_FEATURE_TRANSFER_DST_BIT"],
+                        "bufferFeatures": []
+                    }
+                }
+            }
+        }
+    },
+    "profiles": {
+        "VP_ANDROID_baseline_2022": {
+            "version": 1,
+            "api-version": "1.1.106",
+            "label": "Android Vulkan Baseline 2022 profile",
+            "description": "Collection of functionality that is broadly supported on Android",
+            "contributors": {
+                "Trevor David Black": {
+                    "company": "Google",
+                    "email": "vantablack@google.com",
+                    "contact": true
+                },
+                "Ian Elliott": {
+                    "company": "Google",
+                    "email": "ianelliott@google.com",
+                    "contact": true
+                }
+            },
+            "history": [
+                {
+                    "revision": 1,
+                    "date": "2022-12-23",
+                    "author": "Trevor David Black",
+                    "comment": "Final draft"
+                }
+            ],
+            "capabilities": [
+                "baseline"
+            ]
+        }
+    }
+}
diff --git a/vulkan/vkprofiles/vkprofiles.cpp b/vulkan/vkprofiles/vkprofiles.cpp
new file mode 100644
index 0000000..465dc25
--- /dev/null
+++ b/vulkan/vkprofiles/vkprofiles.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#define LOG_TAG "vkprofiles"
+
+#ifndef VK_USE_PLATFORM_ANDROID_KHR
+#define VK_USE_PLATFORM_ANDROID_KHR
+#endif
+
+#include <string>
+#include <vector>
+
+#include <android/log.h>
+
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+
+#include "generated/vulkan_profiles.h"
+#include "vkprofiles.h"
+
+namespace android::vkprofiles {
+
+/* Wrap vkProfileGetSupport in an anonymous namespace.
+ * vkProfileGetSupport only works for profiles that we explicitly add, we don't
+ * want a user of this library to mistakenly call with a profile that we haven't
+ * added.
+ */
+namespace {
+
+std::string vkProfileGetSupport(const VpProfileProperties* pProfile,
+                                const uint32_t minApiVersion) {
+    VkResult result = VK_SUCCESS;
+    VkBool32 supported = VK_FALSE;
+
+    result = vpGetInstanceProfileSupport(nullptr, pProfile, &supported);
+    if (result != VK_SUCCESS) {
+        std::string error(
+            "There was a failure from vpGetInstanceProfileSupport,"
+            " check `vkprofiles` in logcat."
+            " result = " +
+            std::to_string(result));
+        return error;
+    }
+    if (supported != VK_TRUE) {
+        std::string error(
+            "There was a failure from vpGetInstanceProfileSupport,"
+            " check `vkprofiles` in logcat."
+            " supported = " +
+            std::to_string(supported));
+        return error;
+    }
+
+    const VkApplicationInfo appInfo = {
+        VK_STRUCTURE_TYPE_APPLICATION_INFO,
+        nullptr,
+        "vkprofiles",
+        0,
+        "",
+        0,
+        minApiVersion,
+    };
+    VkInstanceCreateInfo instanceCreateInfo = {
+        VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+        nullptr,
+        0,
+        &appInfo,
+        0,
+        nullptr,
+        0,
+        nullptr,
+    };
+
+    VpInstanceCreateInfo vpInstanceCreateInfo{};
+    vpInstanceCreateInfo.pCreateInfo = &instanceCreateInfo;
+    vpInstanceCreateInfo.enabledFullProfileCount = 1;
+    vpInstanceCreateInfo.pEnabledFullProfiles = pProfile;
+
+    VkInstance instance = VK_NULL_HANDLE;
+    result = vpCreateInstance(&vpInstanceCreateInfo, nullptr, &instance);
+    if (result != VK_SUCCESS) {
+        std::string error(
+            "There was a failure from vpCreateInstance,"
+            " check `vkprofiles` in logcat."
+            " result = " +
+            std::to_string(result));
+        return error;
+    }
+
+    uint32_t count;
+    result = vkEnumeratePhysicalDevices(instance, &count, nullptr);
+    if (result != VK_SUCCESS) {
+        vkDestroyInstance(instance, nullptr);
+        std::string error(
+            "There was a failure from vkEnumeratePhysicalDevices,"
+            " check `vkprofiles` in logcat."
+            " result = " +
+            std::to_string(result));
+        return error;
+    }
+
+    std::vector<VkPhysicalDevice> devices(count, VK_NULL_HANDLE);
+    result = vkEnumeratePhysicalDevices(instance, &count, devices.data());
+    if (result != VK_SUCCESS) {
+        vkDestroyInstance(instance, nullptr);
+        std::string error(
+            "There was a failure from vkEnumeratePhysicalDevices (2),"
+            " check `vkprofiles` in logcat."
+            " result = " +
+            std::to_string(result));
+        return error;
+    }
+
+    bool onePhysicalDeviceSupports = false;
+    for (size_t i = 0; i < count; i++) {
+        result = vpGetPhysicalDeviceProfileSupport(instance, devices[i],
+                                                   pProfile, &supported);
+        if (result != VK_SUCCESS) {
+            ALOGD("vpGetPhysicalDeviceProfileSupport fail, result = %d",
+                  result);
+            continue;
+        } else if (supported != VK_TRUE) {
+            ALOGD("vpGetPhysicalDeviceProfileSupport fail, supported = %d",
+                  supported);
+            continue;
+        }
+
+        onePhysicalDeviceSupports = true;
+    }
+
+    if (!onePhysicalDeviceSupports) {
+        std::string error(
+            "There was a failure from vpGetPhysicalDeviceProfileSupport,"
+            " check `vkprofiles` in logcat."
+            " No VkPhysicalDevice supports the profile");
+        return error;
+    }
+
+    return std::string("SUPPORTED");
+}
+
+}  // anonymous namespace
+
+std::string vkAbp2021GetSupport() {
+    VpProfileProperties profile{VP_ANDROID_BASELINE_2021_NAME,
+                                VP_ANDROID_BASELINE_2021_SPEC_VERSION};
+    return vkProfileGetSupport(&profile,
+                               VP_ANDROID_BASELINE_2021_MIN_API_VERSION);
+}
+
+std::string vkAbp2021CpuOnlyGetSupport() {
+    VpProfileProperties profile{VP_ANDROID_BASELINE_2021_CPU_ONLY_NAME,
+                                VP_ANDROID_BASELINE_2021_CPU_ONLY_SPEC_VERSION};
+    return vkProfileGetSupport(&profile,
+                               VP_ANDROID_BASELINE_2021_MIN_API_VERSION);
+}
+
+std::string vkAbp2022GetSupport() {
+    VpProfileProperties profile{VP_ANDROID_BASELINE_2022_NAME,
+                                VP_ANDROID_BASELINE_2022_SPEC_VERSION};
+    return vkProfileGetSupport(&profile,
+                               VP_ANDROID_BASELINE_2022_MIN_API_VERSION);
+}
+
+std::string vkVpa15GetSupport() {
+    VpProfileProperties profile{VP_ANDROID_15_MINIMUMS_NAME,
+                                VP_ANDROID_15_MINIMUMS_SPEC_VERSION};
+    return vkProfileGetSupport(&profile,
+                               VP_ANDROID_15_MINIMUMS_MIN_API_VERSION);
+}
+
+std::string vkProfiles() {
+    return "{"
+           "\"" + std::string(VP_ANDROID_BASELINE_2021_NAME) + "\": "
+           "\"" + vkAbp2021GetSupport() + "\","
+           "\"" + std::string(VP_ANDROID_BASELINE_2021_CPU_ONLY_NAME) + "\": "
+           "\"" + vkAbp2021CpuOnlyGetSupport() + "\","
+           "\"" + std::string(VP_ANDROID_BASELINE_2022_NAME) + "\": "
+           "\"" + vkAbp2022GetSupport() + "\","
+           "\"" + std::string(VP_ANDROID_15_MINIMUMS_NAME) + "\": "
+           "\"" + vkVpa15GetSupport() + "\""
+           "}";
+}
+
+}  // namespace android::vkprofiles
diff --git a/vulkan/vkprofiles/vkprofiles.h b/vulkan/vkprofiles/vkprofiles.h
new file mode 100644
index 0000000..8f42e9b
--- /dev/null
+++ b/vulkan/vkprofiles/vkprofiles.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 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 <string>
+
+namespace android::vkprofiles {
+
+/*
+ * vk**GetSupport is a function call to determine if the device supports a
+ * specific Vulkan Profile. These functions call into
+ * generated/vulkan_profiles.h and so only work with select profiles. If the
+ * device supports the profile, the string "SUPPORTED" is returned, otherwise an
+ * error message is returned.
+ */
+std::string vkAbp2021GetSupport();
+std::string vkAbp2021GetSupportCpuOnly();
+std::string vkAbp2022GetSupport();
+std::string vkVpa15GetSupport();
+
+// Returns a json string that enumerates support for any of the Vulkan profiles
+// specified in the above functions
+std::string vkProfiles();
+
+}  // namespace android::vkprofiles